<!doctype html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>



<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.0" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="kniost, NexT" />





  <link rel="alternate" href="/atom.xml" title="结网之路" type="application/atom+xml" />




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.1.0" />






<meta name="description" content="kniost 的成长与疑惑">
<meta property="og:type" content="website">
<meta property="og:title" content="结网之路">
<meta property="og:url" content="https://kniost.github.io/index.html">
<meta property="og:site_name" content="结网之路">
<meta property="og:description" content="kniost 的成长与疑惑">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="结网之路">
<meta name="twitter:description" content="kniost 的成长与疑惑">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    sidebar: {"position":"right","display":"post","offset":12,"offset_float":0,"b2t":false,"scrollpercent":false},
    fancybox: true,
    motion: true,
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://kniost.github.io/"/>





  <title> 结网之路 </title>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  














  
  
    
  

  <div class="container one-collumn sidebar-position-right 
   page-home 
 ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">结网之路</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">临渊羡鱼，不如退而结网</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-03-13-cj0ajvovq00251js6b0dy2nas/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-03-13-cj0ajvovq00251js6b0dy2nas/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十九）—— 第29章 定制视图与触摸事件
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-03-13T11:07:10+08:00">
                2017-03-13
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-03-13-cj0ajvovq00251js6b0dy2nas/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-03-13-cj0ajvovq00251js6b0dy2nas/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了自定义 View 及其触摸事件的处理，有一定的难度</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/d56f3711ddf5c7be62e44199e365682bb6a121a4" target="_blank" rel="external">完成第29章，未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/e2dc19ea90c7ceddb52217283cb0111c98f5d22d" target="_blank" rel="external">完成第29章挑战1-设备旋转</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/5819fc03bde8009a8fd1683f29056f3ff8df4427" target="_blank" rel="external">完成第29章挑战2-双指旋转矩形</a></p>
</blockquote>
<h1 id="1-自定义-View（定制视图）"><a href="#1-自定义-View（定制视图）" class="headerlink" title="1. 自定义 View（定制视图）"></a>1. 自定义 View（定制视图）</h1><p>Android 自带众多优秀的标准视图与组件，但有时为追求独特的应用视觉效果，我们仍需创建定制视图。尽管定制视图种类繁多，但无外乎分为以下两大类别。</p>
<ul>
<li>简单视图。简单视图内部也可以很复杂;之所以归为简单类别，是因为简单视图不包括子视图。而且，简单视图几乎总是会执行定制绘制。</li>
<li>聚合视图。聚合视图由其他视图对象组成。聚合视图通常管理着子视图，但不负责执行定制绘制。图形绘制任务都委托给了各个子视图。<br><strong>创建定制视图所需的三大步骤：</strong></li>
</ul>
<ol>
<li>选择超类。对于简单定制视图而言，View 是个空白画布，因此它作为超类最常见。对于聚合定制视图，我们应选择合适的超类布局，比如 FrameLayout。</li>
<li>继承选定的超类，并至少覆盖一个超类构造方法。</li>
<li>覆盖其他关键方法，以定制视图行为。</li>
</ol>
<h2 id="1-1-创建一个基本的自定义-View"><a href="#1-1-创建一个基本的自定义-View" class="headerlink" title="1.1 创建一个基本的自定义 View"></a>1.1 创建一个基本的自定义 View</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BoxDrawingView</span> <span class="keyword">extends</span> <span class="title">View</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="comment">// 从代码中创建的时候调用</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BoxDrawingView</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">        <span class="keyword">this</span>(context, <span class="keyword">null</span>);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="comment">// 从 xml 文件中 inflate 的时候调用</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BoxDrawingView</span><span class="params">(Context context, AttributeSet attrs)</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>(context, attrs);</div><div class="line">    &#125;</div></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意在引用时我们必须使用自定义 View 的全路径类名，这样布局 inflater 才能够找到它。</strong>布局 inflater 解析布局 XML 文件，并按视图定义创建 View 实例。如果元素名不是全路径类名，布局 inflater 会转而在 android.view 和 android.widget 包中寻找目标。如果目标视图类放置在其他包中，布局 inflater 将无法找到目标并最终导致应用崩溃。</p>
</blockquote>
<h2 id="1-2-处理触摸事件"><a href="#1-2-处理触摸事件" class="headerlink" title="1.2 处理触摸事件"></a>1.2 处理触摸事件</h2><p>因为我们的自定义 View 是 View 的子类，可以直接覆盖以下 View 方法：</p>
<p><code>public boolean onTouchEvent(MotionEvent event)</code></p>
<p>该方法接收一个 MotionEvent 类实例，MotionEvent 类可用来描述包括位置和动作的触摸事件。动作用于描述事件所处的阶段。</p>
<table>
<thead>
<tr>
<th style="text-align:left">动作常量</th>
<th style="text-align:left">动作描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">ACTION_DOWN</td>
<td style="text-align:left">手指触摸到屏幕</td>
</tr>
<tr>
<td style="text-align:left">ACTION_MOVE</td>
<td style="text-align:left">手指在屏幕上移动</td>
</tr>
<tr>
<td style="text-align:left">ACTION_UP</td>
<td style="text-align:left">手指离开屏幕</td>
</tr>
<tr>
<td style="text-align:left">ACTION_CANCEL</td>
<td style="text-align:left">父视图拦截了触摸事件</td>
</tr>
</tbody>
</table>
<p>我们的目的就是在一根手指放下的时候记录下放下的位置，移动时随之变化，放开时固定该矩形框。并且之前画的矩形框数据需要记录下来。<br>所以建立一个实体类用于记录按下的点和放开的点：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Box</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> PointF mOrigin;</div><div class="line">    <span class="keyword">private</span> PointF mCurrent;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Box</span><span class="params">(PointF origin)</span> </span>&#123;</div><div class="line">        mOrigin = origin;</div><div class="line">        mCurrent = origin;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后重写 onTouchEvent 并进行相应操作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> Box mCurrentBox;</div><div class="line"><span class="keyword">private</span> List&lt;Box&gt; mBoxen = <span class="keyword">new</span> ArrayList&lt;&gt;();</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onTouchEvent</span><span class="params">(MotionEvent event)</span> </span>&#123;</div><div class="line">	<span class="comment">// 每次有触摸事件都记录下现在的坐标</span></div><div class="line">    PointF current = <span class="keyword">new</span> PointF(event.getX(), event.getY());</div><div class="line">    String action = <span class="string">""</span>;</div><div class="line"></div><div class="line">    <span class="keyword">switch</span> (event.getActionMasked()) &#123;</div><div class="line">        <span class="keyword">case</span> MotionEvent.ACTION_DOWN:</div><div class="line">            action = <span class="string">"ACTION_DOWN"</span>;</div><div class="line">            <span class="comment">// 每次按下的时候在列表中中新增一个 Box</span></div><div class="line">            mCurrentBox = <span class="keyword">new</span> Box(current);</div><div class="line">            mBoxen.add(mCurrentBox);</div><div class="line">            <span class="keyword">break</span>;</div><div class="line">        <span class="keyword">case</span> MotionEvent.ACTION_MOVE:</div><div class="line">            action = <span class="string">"ACTION_MOVE"</span>;</div><div class="line">            <span class="keyword">if</span> (mCurrentBox != <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="comment">// 移动的时候都要重绘</span></div><div class="line">                mCurrentBox.setCurrent(current);</div><div class="line">                invalidate();</div><div class="line">            &#125;</div><div class="line">            <span class="keyword">break</span>;</div><div class="line">        <span class="keyword">case</span> MotionEvent.ACTION_UP:</div><div class="line">        	<span class="comment">// 抬起的时候不再指向最新的 Box</span></div><div class="line">            action = <span class="string">"ACTION_UP"</span>;</div><div class="line">            mCurrentBox = <span class="keyword">null</span>;</div><div class="line">            <span class="keyword">break</span>;</div><div class="line">        <span class="keyword">case</span> MotionEvent.ACTION_CANCEL:</div><div class="line">            action = <span class="string">"ACTION_CANCEL"</span>;</div><div class="line">            mCurrentBox = <span class="keyword">null</span>;</div><div class="line">            <span class="keyword">break</span>;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    Log.i(TAG, action + <span class="string">" at x="</span> + current.x +</div><div class="line">           <span class="string">", y="</span> + current.y);</div><div class="line"></div><div class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="2-onDraw-方法内的图形绘制"><a href="#2-onDraw-方法内的图形绘制" class="headerlink" title="2. onDraw() 方法内的图形绘制"></a>2. onDraw() 方法内的图形绘制</h1><p>应用启动时，所有视图都处于无效状态。也就是说，视图还没有绘制到屏幕上。为解决这个问题，Android 调用了顶级 View 视图的 draw()方法。这会引起自上而下的链式调用反应。首先，视图完成自我绘制，然后是子视图的自我绘制，再然后是子视图的子视图的自我绘制，如此调用下去直至继承结构的末端。当继承结构中的所有视图都完成自我绘制后，最顶级 View 视图也就生效了。<br>为加入这种绘制，可覆盖以下 View 方法: <code>protected void onDraw(Canvas canvas)</code><br>Canvas 和 Paint 是 Android 系统的两大绘制类。</p>
<ul>
<li>Canvas 类拥有我们需要的所有绘制操作。其方法可决定绘在哪里以及绘什么，比如线条、<br>圆形、字词、矩形等。</li>
<li>Paint 类决定如何绘制。其方法可指定绘制图形的特征，例如是否填充图形、使用什么字<br>体绘制、线条是什么颜色等。 </li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">BoxDrawingView</span><span class="params">(Context context, AttributeSet attrs)</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>(context, attrs);</div><div class="line"></div><div class="line">    <span class="comment">// 颜色为好看的半透明红色的矩形画笔</span></div><div class="line">    mBoxPaint = <span class="keyword">new</span> Paint();</div><div class="line">    mBoxPaint.setColor(<span class="number">0x22ff0000</span>);</div><div class="line"></div><div class="line">    <span class="comment">// 颜色为米白的背景画笔</span></div><div class="line">    mBackgroundPaint = <span class="keyword">new</span> Paint();</div><div class="line">    mBackgroundPaint.setColor(<span class="number">0xfff8efe0</span>);</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDraw</span><span class="params">(Canvas canvas)</span> </span>&#123;</div><div class="line">    <span class="comment">// 每次画的时候先画出背景</span></div><div class="line">    canvas.drawPaint(mBackgroundPaint);</div><div class="line"></div><div class="line">	<span class="comment">// 然后画出每个绘制过的矩形</span></div><div class="line">    <span class="keyword">for</span> (Box box : mBoxen) &#123;</div><div class="line">        <span class="keyword">float</span> left = Math.min(box.getOrigin().x, box.getCurrent().x);</div><div class="line">        <span class="keyword">float</span> right = Math.max(box.getOrigin().x, box.getCurrent().x);</div><div class="line">        <span class="keyword">float</span> top = Math.min(box.getOrigin().y, box.getCurrent().y);</div><div class="line">        <span class="keyword">float</span> bottom = Math.max(box.getOrigin().y, box.getCurrent().y);</div><div class="line"></div><div class="line">        canvas.drawRect(left, top, right, bottom, mBoxPaint);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="3-挑战练习"><a href="#3-挑战练习" class="headerlink" title="3. 挑战练习"></a>3. 挑战练习</h1><h2 id="3-1-设备旋转问题"><a href="#3-1-设备旋转问题" class="headerlink" title="3.1 设备旋转问题"></a>3.1 设备旋转问题</h2><ol>
<li>首先，要给整个视图加上 ID，<code>onSaveInstanceState()</code>以及<code>onRestoreInstanceState()</code>方法才会被调用</li>
<li>使用 Bundle 传递需要存储的参数</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> Parcelable <span class="title">onSaveInstanceState</span><span class="params">()</span> </span>&#123;</div><div class="line">    Bundle bundle = <span class="keyword">new</span> Bundle();</div><div class="line">    <span class="comment">// 存储父类需要存储的内容</span></div><div class="line">    Parcelable superData = <span class="keyword">super</span>.onSaveInstanceState();</div><div class="line">    bundle.putParcelable(KEY_SUPER_DATA, superData);</div><div class="line">    <span class="comment">// 存储所有的矩形</span></div><div class="line">    bundle.putSerializable(KEY_BOXEN, (ArrayList) mBoxen);</div><div class="line">    <span class="keyword">return</span> bundle;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onRestoreInstanceState</span><span class="params">(Parcelable state)</span> </span>&#123;</div><div class="line">    Bundle bundle = (Bundle) state;</div><div class="line">    <span class="comment">// 取出父类的内容</span></div><div class="line">    Parcelable superData = bundle.getParcelable(KEY_SUPER_DATA);</div><div class="line">    <span class="comment">// 取出存储的矩形</span></div><div class="line">    mBoxen = (List&lt;Box&gt;) bundle.getSerializable(KEY_BOXEN);</div><div class="line">    <span class="keyword">super</span>.onRestoreInstanceState(superData);</div><div class="line">    invalidate();</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="3-2-旋转矩形框"><a href="#3-2-旋转矩形框" class="headerlink" title="3.2 旋转矩形框"></a>3.2 旋转矩形框</h2><ol>
<li>在处理多点触控时我们需要用 <code>MotionEvent.getActionMasked()</code> 方法来获取事件 ID，<code>ACTION_POINTER_DOWN</code>指的是屏幕上已经有手指了（无论是几根，最大不超过【多点触控屏的极限 - 1】），另一根手指按下的情况。也就是说此时我们能知道两个手指按下了。</li>
<li><p>其次，图形的旋转一般是在绘制的时候旋转画布（canvas），需要的参数有旋转的角度（用度表示）以及旋转中心坐标，在这里我在 Box 类中加入了最开始的角度 <code>mOriginAngle</code>，已旋转后的角度 <code>mRotatedAngle</code> 两个成员变量，以及一个获取中心点坐标的方法。</p>
 <figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Box</span> </span>&#123;</div><div class="line">    	<span class="keyword">private</span> PointF mOrigin;</div><div class="line">    	<span class="keyword">private</span> PointF mCurrent;</div><div class="line">    	<span class="comment">// 此次按下时的角度</span></div><div class="line">    	<span class="keyword">private</span> <span class="keyword">float</span> mOriginAngle;</div><div class="line">    	<span class="keyword">private</span> <span class="keyword">float</span> mRotatedAngle; <span class="comment">// 已旋转的角度</span></div><div class="line"></div><div class="line">    	<span class="function"><span class="keyword">public</span> <span class="title">Box</span><span class="params">(PointF origin)</span> </span>&#123;</div><div class="line">        	mOrigin = origin;</div><div class="line">        	mCurrent = origin;</div><div class="line">        	mOriginAngle = <span class="number">0</span>;</div><div class="line">        	mRotatedAngle = <span class="number">0</span>;</div><div class="line">    	&#125;</div><div class="line">		<span class="comment">/** 省略 Getter 和 Setter **/</span></div><div class="line">	</div><div class="line">		<span class="comment">// 获取矩形的中心点</span></div><div class="line">    	<span class="function"><span class="keyword">public</span> PointF <span class="title">getCenter</span><span class="params">()</span> </span>&#123;</div><div class="line">        	<span class="keyword">return</span> <span class="keyword">new</span> PointF(</div><div class="line">                (mCurrent.x + mOrigin.x) / <span class="number">2</span>, </div><div class="line">                (mCurrent.y + mOrigin.y) / <span class="number">2</span>);</div><div class="line">    	&#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
</li>
<li><p>对不同的触摸情况进行处理：</p>
 <figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onTouchEvent</span><span class="params">(MotionEvent event)</span> </span>&#123;</div><div class="line">       PointF current = <span class="keyword">new</span> PointF(event.getX(), event.getY());</div><div class="line">       String action = <span class="string">""</span>;</div><div class="line"></div><div class="line">	<span class="comment">// 省略没有变化的部分</span></div><div class="line">       <span class="keyword">switch</span> (event.getActionMasked()) &#123;</div><div class="line">           <span class="keyword">case</span> MotionEvent.ACTION_POINTER_DOWN:</div><div class="line">               action = <span class="string">"POINTER_DOWN"</span>;</div><div class="line">               <span class="keyword">if</span> (event.getPointerCount() == <span class="number">2</span>) &#123;</div><div class="line">               <span class="comment">// 首先获取按下时的角度（有一个弧度转角度的过程）</span></div><div class="line">               <span class="comment">// 每次按下的时候将角度存入现在矩形的原始角度</span></div><div class="line">                   <span class="keyword">float</span> angle = (<span class="keyword">float</span>) (Math.atan((event.getY(<span class="number">1</span>) - event.getY(<span class="number">0</span>)) /</div><div class="line">                       (event.getX(<span class="number">1</span>) - event.getX(<span class="number">0</span>))) * <span class="number">180</span> / Math.PI);</div><div class="line">                   mCurrentBox.setOriginAngle(angle);</div><div class="line">               &#125;</div><div class="line">               <span class="keyword">break</span>;</div><div class="line"></div><div class="line">           <span class="keyword">case</span> MotionEvent.ACTION_MOVE:</div><div class="line">               action = <span class="string">"ACTION_MOVE"</span>;</div><div class="line">               <span class="keyword">if</span> (mCurrentBox != <span class="keyword">null</span>) &#123;</div><div class="line">               	<span class="comment">// 如果只有一只手指按下，而且还未曾旋转过的话，就进行大小的缩放</span></div><div class="line">                   <span class="keyword">if</span> (event.getPointerCount() == <span class="number">1</span> &amp;&amp; mCurrentBox.getRotatedAngle() == <span class="number">0</span>) &#123;</div><div class="line">                       mCurrentBox.setCurrent(current);</div><div class="line">                   &#125;</div><div class="line">                   <span class="comment">// 如果按下了两根手指</span></div><div class="line">                   <span class="keyword">if</span> (event.getPointerCount() == <span class="number">2</span>) &#123;</div><div class="line">                   	<span class="comment">// 获取角度</span></div><div class="line">                       <span class="keyword">float</span> angle = (<span class="keyword">float</span>) (Math.atan((event.getY(<span class="number">1</span>) - event.getY(<span class="number">0</span>)) /</div><div class="line">                               (event.getX(<span class="number">1</span>) - event.getX(<span class="number">0</span>))) * <span class="number">180</span> / Math.PI);</div><div class="line">                       Log.i(TAG, <span class="string">"onTouchEvent: angle:"</span> + (angle - mCurrentBox.getOriginAngle()));</div><div class="line">                       <span class="comment">// 已旋转的角度 = 之前旋转的角度 + 新旋转的角度</span></div><div class="line">                       <span class="comment">// 新旋转的角度 = 本次 move 到的角度 - 手指按下的角度</span></div><div class="line">                       mCurrentBox.setRotatedAngle(mCurrentBox.getRotatedAngle() + angle</div><div class="line">                               - mCurrentBox.getOriginAngle());</div><div class="line">                       <span class="comment">// 旋转角度变化后，初始角度也发生变化</span></div><div class="line">                       mCurrentBox.setOriginAngle(angle);</div><div class="line">                   &#125;</div><div class="line">                   invalidate();</div><div class="line">               &#125;</div><div class="line">               <span class="keyword">break</span>;</div><div class="line">       &#125;</div><div class="line"></div><div class="line">       <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">   &#125;</div></pre></td></tr></table></figure></li>
</ol>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-03-02-cj0ajvovl001w1js6jq55crvh/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-03-02-cj0ajvovl001w1js6jq55crvh/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十八）—— 第28章 网页浏览
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-03-02T11:17:10+08:00">
                2017-03-02
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-03-02-cj0ajvovl001w1js6jq55crvh/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-03-02-cj0ajvovl001w1js6jq55crvh/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲的是使用 WebView 在应用内浏览网页</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/bedc8d10fb81e2936afd669288ef4e6fbd65183a" target="_blank" rel="external">完成第28章，未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/f58ff9aa76d6a025bddcddb8feda323f0de3a0c4" target="_blank" rel="external">完成第28章挑战</a></p>
</blockquote>
<h1 id="1-WebView"><a href="#1-WebView" class="headerlink" title="1. WebView"></a>1. WebView</h1><p>如果不使用 WebView，我们可以使用隐式 intent，也就是用独立的浏览器打开一个网页。但是我们通常只想在 activity 中显示网页内容而不是打开浏览器：或许是想显示自己生成的 HTML，或许是想以某种方式限制用户使用浏览器。对于大多数需要帮助文档的应用，普遍做法是以网页的形式提供帮助文档，这样会方便后期的更新与维护。打开浏览器查看帮助文档，既不专业，又妨碍应用行为的定制，同时也无法将网页整合进自己的用户界面。</p>
<h2 id="1-1-WebView-的使用"><a href="#1-1-WebView-的使用" class="headerlink" title="1.1 WebView 的使用"></a>1.1 WebView 的使用</h2><p>和其他的 View 一样，在 fragment 的布局文件中声明之后，在 fragment 中获取实例，然后进行一番设置：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">RelativeLayout</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line">                <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></div><div class="line">                <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span>&gt;</div><div class="line">    <span class="tag">&lt;<span class="name">WebView</span></span></div><div class="line">        <span class="attr">android:id</span>=<span class="string">"@+id/fragment_photo_page_web_view"</span></div><div class="line">        <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></div><div class="line">        <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span>/&gt;</div><div class="line"><span class="tag">&lt;/<span class="name">RelativeLayout</span>&gt;</span></div></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PhotoPageFragment</span> <span class="keyword">extends</span> <span class="title">VisibleFragment</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ARG_URI = <span class="string">"photo_page_url"</span>;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> Uri mUri;</div><div class="line">    <span class="keyword">private</span> WebView mWebView;</div><div class="line"></div><div class="line">	………………</div><div class="line">	</div><div class="line">	<span class="comment">// 在这里防止 Lint 警告 JavaScript 被启用</span></div><div class="line">    <span class="meta">@SuppressLint</span>(<span class="string">"SetJavaScriptEnabled"</span>)</div><div class="line">    <span class="meta">@Nullable</span></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> View <span class="title">onCreateView</span><span class="params">(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)</span> </span>&#123;</div><div class="line">        View v = inflater.inflate(R.layout.fragment_photo_page, container, <span class="keyword">false</span>);</div><div class="line"></div><div class="line">		<span class="comment">// 首先获取实例</span></div><div class="line">        mWebView = (WebView) v.findViewById(R.id.fragment_photo_page_web_view);</div><div class="line">        <span class="comment">// 然后将 JavaScript 设置为启用</span></div><div class="line">        mWebView.getSettings().setJavaScriptEnabled(<span class="keyword">true</span>);</div><div class="line">        mWebView.setWebViewClient(<span class="keyword">new</span> WebViewClient() &#123;</div><div class="line">        	<span class="comment">// 这个方法决定 url 在哪里处理，返回 false 代表让 WebView 去加载</span></div><div class="line">        	<span class="comment">// 其实该方法默认返回的就是 false</span></div><div class="line">            <span class="meta">@Override</span></div><div class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">shouldOverrideUrlLoading</span><span class="params">(WebView view, String url)</span> </span>&#123;</div><div class="line">                <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">            &#125;</div><div class="line">        &#125;);</div><div class="line">        mWebView.loadUrl(mUri.toString());</div><div class="line"></div><div class="line">        <span class="keyword">return</span> v;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>可以看到，WebClient 是一个关键，如果不重写该方法，WebView 可能不会加载这个 url</p>
<h2 id="1-2-使用-WebChromeClient-优化-WebView-显示"><a href="#1-2-使用-WebChromeClient-优化-WebView-显示" class="headerlink" title="1.2 使用 WebChromeClient 优化 WebView 显示"></a>1.2 使用 WebChromeClient 优化 WebView 显示</h2><p>WebViewClient 主要帮助 WebView 处理各种通知、请求事件，而 WebChromeClient 主要辅助 WebView 处理 Javascript 的对话框、网站图标、网站 title、加载进度等。</p>
<p>我们在视图中加入 ProgressBar 放在 WebView 上方并默认隐藏，由于 WebChromeClient 返回进度值在0～100之间，所以我们要设置进度条的最大值为100。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line">mProgressBar.setMax(<span class="number">100</span>); <span class="comment">// WebChromeClient 进度范围就是0～100</span></div><div class="line"></div><div class="line">mWebView.setWebChromeClient(<span class="keyword">new</span> WebChromeClient() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProgressChanged</span><span class="params">(WebView view, <span class="keyword">int</span> newProgress)</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (newProgress == <span class="number">100</span>) &#123;</div><div class="line">            mProgressBar.setVisibility(View.GONE);</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            mProgressBar.setVisibility(View.VISIBLE);</div><div class="line">            mProgressBar.setProgress(newProgress);</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceivedTitle</span><span class="params">(WebView view, String title)</span> </span>&#123;</div><div class="line">        AppCompatActivity activity = (AppCompatActivity) getActivity();</div><div class="line">        设置网站标题</div><div class="line">        activity.getSupportActionBar().setSubtitle(title);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
<h2 id="1-3-处理-WebView-的设备旋转问题"><a href="#1-3-处理-WebView-的设备旋转问题" class="headerlink" title="1.3 处理 WebView 的设备旋转问题"></a>1.3 处理 WebView 的设备旋转问题</h2><p>尝试旋转设备屏幕，可以发现 WebView 必须重新加载网页。这是因为 WebView 包含太多的数据，无法在 onSaveInstanceState(…)方法内全部保存。所以每次设备旋转，它都必须从头开始加载网页数据。</p>
<p>因为 WebView 是视图层级结构的一部分，所以旋转后它肯定会销毁并重建，不能用 retainFragment 来保留。对于一些类似的类(如 VideoView)，Android 文档推荐让 activity 自己处理设备配置变更。也就是说，无需销毁重建 activity，就能直接调整自己的视图以适应新的屏幕尺寸。这样，WebView 也就不必重新加载全部数据了。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">activity</span> <span class="attr">android:name</span>=<span class="string">".PhotoPageActivity"</span></span></div><div class="line">	<span class="attr">android:configChanges</span>=<span class="string">"keyboardHidden|orientation|screenSize"</span>/&gt;</div></pre></td></tr></table></figure>
<p>android:configChanges 属性表明，如果因键盘开或关、屏幕方向改变、屏幕大小改变(也包括 Android 3.2之后的屏幕方向变化)而发生设备配置更改，那么 activity 应自己处理配置更改。</p>
<h1 id="2-挑战练习"><a href="#2-挑战练习" class="headerlink" title="2. 挑战练习"></a>2. 挑战练习</h1><h2 id="2-1-使用后退键浏览历史页面"><a href="#2-1-使用后退键浏览历史页面" class="headerlink" title="2.1 使用后退键浏览历史页面"></a>2.1 使用后退键浏览历史页面</h2><p>我们覆盖 Activity.onBackPressed 方法，就可以对返回键的行为进行控制，但是要获取 WebView 的实例，所以在 fragment 中加入了一个 getter。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// PhotoPageActivity.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onBackPressed</span><span class="params">()</span> </span>&#123;</div><div class="line">    PhotoPageFragment fragment = (PhotoPageFragment) getSupportFragmentManager()</div><div class="line">            .findFragmentById(R.id.fragment_container);</div><div class="line">    <span class="keyword">if</span> (fragment.getWebView().canGoBack()) &#123;</div><div class="line">        fragment.getWebView().goBack();</div><div class="line">    &#125; <span class="keyword">else</span> &#123;</div><div class="line">        <span class="keyword">super</span>.onBackPressed();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="2-2-非-HTTP-链接支持"><a href="#2-2-非-HTTP-链接支持" class="headerlink" title="2.2 非 HTTP 链接支持"></a>2.2 非 HTTP 链接支持</h2><p>只需要修改重写的 shouldOverrideUrlLoading 方法即可。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">mWebView.setWebViewClient(<span class="keyword">new</span> WebViewClient() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">shouldOverrideUrlLoading</span><span class="params">(WebView view, String url)</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (url.startsWith(<span class="string">"https://"</span>) || url.startsWith(<span class="string">"http://"</span>)) &#123;</div><div class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            Intent intent = <span class="keyword">new</span> Intent(Intent.ACTION_VIEW, Uri.parse(url));</div><div class="line">            startActivity(intent);</div><div class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-03-01-cj0ajvovm001z1js6va278bhl/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-03-01-cj0ajvovm001z1js6va278bhl/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十七）—— 第27章 broadcast intent
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-03-01T15:15:10+08:00">
                2017-03-01
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-03-01-cj0ajvovm001z1js6va278bhl/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-03-01-cj0ajvovm001z1js6va278bhl/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲的是 Android 系统的广播机制，并介绍了广播的使用，权限以及有序广播的用法。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/762ad934bfe2cd3c50f74cd881875da46eb391d5" target="_blank" rel="external">完成第27章</a></p>
</blockquote>
<p>在使用广播之前，首先回顾一下 PhotoGallery 在本章之前的逻辑：</p>
<ol>
<li>打开程序后，如果开始推送服务，就每隔一段时间获取一次图片信息</li>
<li>如果图片有更新，就发出通知</li>
</ol>
<p>那么本章我们想做到的有：</p>
<ol>
<li>在开机以后自动启用服务（如果打开了开关）</li>
<li>在应用打开时图片有更新也不发出通知</li>
</ol>
<p>这里，我们将使用广播来完成这些任务。</p>
<h1 id="1-接收系统广播：重启后唤醒"><a href="#1-接收系统广播：重启后唤醒" class="headerlink" title="1. 接收系统广播：重启后唤醒"></a>1. 接收系统广播：重启后唤醒</h1><h2 id="1-1-broadcast-intent"><a href="#1-1-broadcast-intent" class="headerlink" title="1.1 broadcast intent"></a>1.1 broadcast intent</h2><p>Android 设备中，各种事件一直在频繁地发生。Wi-Fi 信号时有时无，各种软件包获得安装，电话不时呼入，短信频繁接收等等。许多系统组件需要知道某些事件的发生。为满足这样的需求，Android 提供了 broadcast intent 组件。broadcast intent 的工作原理类似于之前学过的 intent 唯，一不同的是 broadcast intent 可同时被多个叫作 broadcast receiver 的组件接收。</p>
<h2 id="1-2-standalone-receiver"><a href="#1-2-standalone-receiver" class="headerlink" title="1.2 standalone receiver"></a>1.2 standalone receiver</h2><p>standalone receiver 是一个在 manifest 配置文件中声明的 broadcast receiver。即便应用进程已消亡，standalone receiver 也可以被激活。(另一种就是可以同 fragment 或 activity 的生命周期绑定的 dynamic receiver。)</p>
<p>首先建立这样一个 BroadcastReceiver，并重写 onReceive 方法，<strong>注意：该方法是在主线程中执行的</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StartupReceiver</span> <span class="keyword">extends</span> <span class="title">BroadcastReceiver</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"StartupReceiver"</span>;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceive</span><span class="params">(Context context, Intent intent)</span> </span>&#123;</div><div class="line">        Log.i(TAG, <span class="string">"Received broadcast intent: "</span> + intent.getAction());</div><div class="line"></div><div class="line">        <span class="keyword">boolean</span> isOn = QueryPreferences.isAlarmOn(context);</div><div class="line">        PollService.setServiceAlarm(context, isOn);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>记得在 manifest 文件中声明</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.RECEIVE_BOOT_COMPLETED"</span>/&gt;</span></div><div class="line"></div><div class="line"><span class="tag">&lt;<span class="name">application</span>&gt;</span></div><div class="line">	……</div><div class="line">    <span class="tag">&lt;<span class="name">receiver</span> <span class="attr">android:name</span>=<span class="string">".StartupReceiver"</span>&gt;</span></div><div class="line">        <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></div><div class="line">            <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">"android.intent.action.BOOT_COMPLETED"</span>/&gt;</span></div><div class="line">        <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></div><div class="line">    <span class="tag">&lt;/<span class="name">receiver</span>&gt;</span></div><div class="line">    ……</div><div class="line"><span class="tag">&lt;/<span class="name">application</span>&gt;</span></div></pre></td></tr></table></figure>
<p>登记好 broadcast receiver 以后，一旦设备启动，这个 receiver 就能接收到启动完成的广播，并随之启动服务了。</p>
<h1 id="2-过滤应用在前台时的通知"><a href="#2-过滤应用在前台时的通知" class="headerlink" title="2. 过滤应用在前台时的通知"></a>2. 过滤应用在前台时的通知</h1><p>为了实现这一点，我们把发出通知的思路改了：之前是在服务中查询到新的结果就发出通知，现在则是：</p>
<ol>
<li>在查询到新的结果后，发出一条应用内的广播并在其中标记一个代码 A，</li>
<li>在应用中动态登记广播接收器，如果接收到广播（说明应用在前台），就把这个代码改成 B。</li>
<li>最后总有一个优先级最低的接收器接收到这个广播，如果代码是 A，就发出通知，否则就不发出通知。</li>
</ol>
<h2 id="2-1-发送-broadcast-intent"><a href="#2-1-发送-broadcast-intent" class="headerlink" title="2.1 发送 broadcast intent"></a>2.1 发送 broadcast intent</h2><p>在 Context 类中直接调用 sendBroadcast(Intent) 即可发出广播。但是为了只让本应用接收到该广播，我们在 manifest 文件中声明一个权限并使用：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">android:name</span>=<span class="string">"com.kniost.photogallery.PRIVATE"</span></span></div><div class="line">                <span class="attr">android:protectionLevel</span>=<span class="string">"signature"</span> /&gt;</div><div class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"com.kniost.photogallery.PRIVATE"</span> /&gt;</span></div></pre></td></tr></table></figure>
<p>然后使用 sendBroadcast(Intent intent, String permission) 发送通知即可</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">sendBroadcast(<span class="keyword">new</span> Intent(ACTION_SHOW_NOTIFICATION), PERM_PRIVATE);</div></pre></td></tr></table></figure>
<h2 id="2-2-动态-broadcast-receiver"><a href="#2-2-动态-broadcast-receiver" class="headerlink" title="2.2 动态 broadcast receiver"></a>2.2 动态 broadcast receiver</h2><p>我们要只在应用开启的时候接受发过来的广播过滤，就不能在 manifest 中声明一个过滤器，而是要动态地建立一个广播接收器。我们在这里建立一个用于隐藏前台通知的通用 fragment 子类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">VisibleFragment</span> <span class="keyword">extends</span> <span class="title">Fragment</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"VisibleFragment"</span>;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStart</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>.onStart();</div><div class="line">        IntentFilter filter = <span class="keyword">new</span> IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);</div><div class="line">        getActivity().registerReceiver(mOnShowNotification, filter,</div><div class="line">                PollService.PERM_PRIVATE, <span class="keyword">null</span>);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStop</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>.onStop();</div><div class="line">        getActivity().unregisterReceiver(mOnShowNotification);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> BroadcastReceiver mOnShowNotification = <span class="keyword">new</span> BroadcastReceiver() &#123;</div><div class="line">        <span class="meta">@Override</span></div><div class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceive</span><span class="params">(Context context, Intent intent)</span> </span>&#123;</div><div class="line">            <span class="comment">// 如果接收到广播，说明应用正在前台，所以把 ResultCode 更改掉</span></div><div class="line">            Log.i(TAG, <span class="string">"canceling notification"</span>);</div><div class="line">            setResultCode(Activity.RESULT_CANCELED);</div><div class="line">        &#125;</div><div class="line">    &#125;;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<blockquote>
<p>为什么在 start 和 stop 中登记和撤销 receiver 呢？因为在 retain fragment 中 onCreate(…)和 onDestroy()方法中的 getActivity()方法在设备旋转时会返回不同的值。因此如果想在 Fragment.onCreate(Bundle)和 Fragment.onDestroy()方法中实现登记或撤销登记，应使用 getActivity().getApplicationContext()方法。</p>
</blockquote>
<h2 id="2-3-使用有序-broadcast"><a href="#2-3-使用有序-broadcast" class="headerlink" title="2.3 使用有序 broadcast"></a>2.3 使用有序 broadcast</h2><p>如果想让程序在打开时不发送出通知，就不能再让服务来发出通知了，因为它无法知道前台的运行状态。所以我们让 PollService 发送一个有序广播。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">Notification notification = ……;</div><div class="line"></div><div class="line">Intent i = <span class="keyword">new</span> Intent(ACTION_SHOW_NOTIFICATION);</div><div class="line">i.putExtra(REQUEST_CODE, <span class="number">0</span>);</div><div class="line">i.putExtra(NOTIFICATION, notification);</div><div class="line">sendOrderedBroadcast(i, PERM_PRIVATE, <span class="keyword">null</span>, <span class="keyword">null</span>,</div><div class="line">        Activity.RESULT_OK, <span class="keyword">null</span>, <span class="keyword">null</span>);</div></pre></td></tr></table></figure>
<p>有序广播是按照优先级发送的，先发送给优先级高的接收器，再发给优先级低的接收器。因为在应用结束后也要发出通知，显然我们发出通知的广播接收器是需要声明在 manifest 文件中的。</p>
<p>内部实现如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NotificationReceiver</span> <span class="keyword">extends</span> <span class="title">BroadcastReceiver</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"NotificaitonReceiver"</span>;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceive</span><span class="params">(Context context, Intent intent)</span> </span>&#123;</div><div class="line">        Log.i(TAG, <span class="string">"received result: "</span> + getResultCode());</div><div class="line">        <span class="keyword">if</span> (getResultCode() != Activity.RESULT_OK) &#123;</div><div class="line">            <span class="comment">// PollService 发出的 intent 带的结果码是 RESULT_OK</span></div><div class="line">            <span class="comment">// 如果接到的不是，说明应用在前台，将结果码修改了</span></div><div class="line">            <span class="keyword">return</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">			<span class="comment">// 如果没有 return，说明应用不在前台，就可以发出通知了。</span></div><div class="line">        <span class="keyword">int</span> requestCode = intent.getIntExtra(PollService.REQUEST_CODE, <span class="number">0</span>);</div><div class="line">        Notification notification = (Notification)</div><div class="line">                intent.getParcelableExtra(PollService.NOTIFICATION);</div><div class="line"></div><div class="line">        NotificationManagerCompat notificationManager =</div><div class="line">                NotificationManagerCompat.from(context);</div><div class="line">        notificationManager.notify(requestCode, notification);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">receiver</span> <span class="attr">android:name</span>=<span class="string">".NotificationReceiver"</span></span></div><div class="line">          <span class="attr">android:exported</span>=<span class="string">"false"</span>&gt;</div><div class="line">    <span class="comment">&lt;!-- 在这里将优先级设为最低，即 -999 --&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">intent-filter</span></span></div><div class="line">        <span class="attr">android:priority</span>=<span class="string">"-999"</span>&gt;</div><div class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">"com.kniost.photogallery.SHOW_NOTIFICATION"</span> /&gt;</span></div><div class="line">    <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">receiver</span>&gt;</span></div></pre></td></tr></table></figure>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-23-cj0ajvovo00221js69nx2lou8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-23-cj0ajvovo00221js69nx2lou8/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十六）—— 第26章 后台服务
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-23T12:59:10+08:00">
                2017-02-23
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-23-cj0ajvovo00221js69nx2lou8/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-23-cj0ajvovo00221js69nx2lou8/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了 Android 的一大组件：服务。使用 IntentService 作为后台服务，用 AlarmManager 定时启动，以及应用通知的发出，还介绍了新的 JobScheduler 及其使用。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/5d5771376dd12ca3d41cc52939707297d29e5aa7" target="_blank" rel="external">完成第26章</a></p>
</blockquote>
<p>Activity 就是 Android 应用的前台。所有应用代码都专注于提供良好的用户视觉体验。服务就是 Android 应用的后台，用户无需关心后台发生的一切。即使前台关闭，activity 消失好久了，后台服务依然可以持续不断地工作。<br><strong>服务最关键的特性就是：用户离开当前应用后(打开其他应用或退回主屏幕)，服务依然可以在后台运行。</strong></p>
<h1 id="1-服务的使用"><a href="#1-服务的使用" class="headerlink" title="1. 服务的使用"></a>1. 服务的使用</h1><h2 id="1-1-服务的能与不能"><a href="#1-1-服务的能与不能" class="headerlink" title="1.1 服务的能与不能"></a>1.1 服务的能与不能</h2><p>与 activity 一样，服务是一个有生命周期回调方法的应用组件。这些回调方法同样也会在主 UI 线程上运行。<br>初始创建的服务不会在后台线程上运行任何代码。而大多数重要服务都需要某种后台线程，IntentService 类提供了一套标准实现代码，所以推荐使用 IntentService 完成本章。</p>
<h2 id="1-2-服务的生命周期"><a href="#1-2-服务的生命周期" class="headerlink" title="1.2 服务的生命周期"></a>1.2 服务的生命周期</h2><p>如果是<code>startService(Intent)</code>方法启动的服务，其生命周期很简单，并具有三种生命周期回调方法。</p>
<ol>
<li><code>onCreate(...)</code>方法:服务创建时调用。</li>
<li><code>onStartCommand(Intent,int,int)</code>方法:每次组件通过 startService(Intent)方法<br>启动服务时调用一次。它有两个整数参数，一个是标识符集，一个是启动 ID。标识符集用来表示当前 intent 发送究竟是一次重新发送，还是一次从没成功过的发送。每次调用 onStartCommand(Intent,int,int)方法，启动 ID 都会不同。因此，启动 ID 也可用于区分不同的命令。</li>
<li><code>onDestroy()</code>方法:服务不再需要时调用。通常是在服务停止后。 服务停止时会调用 onDestroy()方法。服务停止的方式取决于服务的类型。</li>
<li>服务的类型由 onStartCommand(…)方法的返回值确定，可能的服务类型有 Service.<code>START_NOT_STICKY</code>、<br><code>START_REDELIVER_INTENT</code>和<code>START_STICKY</code>。<strong>IntentService 是一种 non-sticky 服务</strong>。</li>
</ol>
<h2 id="1-3-不同类型的服务"><a href="#1-3-不同类型的服务" class="headerlink" title="1.3 不同类型的服务"></a>1.3 不同类型的服务</h2><ol>
<li><p><strong>non-sticky 服务</strong><br> non-sticky 服务在服务自己认为已完成任务时停止。为获得 non-sticky 服务，应返回<code>START_NOT_STICKY</code>或<code>START_REDELIVER_INTENT</code>。两者区别在于，如果系统需要在服务完成任务之前关闭它，则服务的具体表现会有所不同。<code>START_NOT_STICKY</code>型服务说消亡就消亡了;而<code>START_REDELIVER_INTENT</code>型服务则会在资源不再吃紧时，尝试再次启动服务。</p>
<blockquote>
<p>通过调用 stopSelf()或 stopSelf(int)方法，我们告诉 Android 任务已完成。stopSelf() 是个无条件方法。不管 onStartCommand(…)方法调用多少次，该方法总是会成功停止服务。stopSelf(int)是个有条件的方法。该方法需要来自于 onStartCommand(…)方法的启动 ID。只有在接收到最新启动 ID 后，该方法才会停止服务。(这也是 IntentService 的后台工作原理。)</p>
</blockquote>
</li>
<li><p><strong>sticky 服务</strong><br>sticky 服务会持续运行，直到外部组件调用 Context.stopService(Intent)方法让它停止。 为获得 sticky 服务，应返回 START_STICKY。<br>sticky 服务启动后会持续运行，除非某个组件调用 Context.stopService(Intent)方法停止它。如因某种原因需终止服务，可传入一个 null intent 给 onStartCommand(…)方法，实现服务的重启。sticky 服务适用于长时间运行的服务，如音乐播放器这种启动后一直保持运行状态，直到用户主动停止的服务。</p>
</li>
</ol>
<h2 id="1-3-服务的使用"><a href="#1-3-服务的使用" class="headerlink" title="1.3 服务的使用"></a>1.3 服务的使用</h2><p>一个最基本的 IntentService 如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PollService</span> <span class="keyword">extends</span> <span class="title">IntentService</span> </span>&#123;</div><div class="line"></div><div class="line">	<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"PollService"</span>;</div><div class="line"></div><div class="line">	<span class="comment">// 外界获取服务的实例</span></div><div class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Intent <span class="title">newIntent</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">		<span class="keyword">return</span> <span class="keyword">new</span> Intent(context, PollService.class);</div><div class="line">	&#125;</div><div class="line"></div><div class="line">	<span class="function"><span class="keyword">public</span> <span class="title">PollService</span><span class="params">()</span> </span>&#123;</div><div class="line">		<span class="keyword">super</span>(TAG);</div><div class="line">	&#125;</div><div class="line">	</div><div class="line">	<span class="comment">// 服务主要执行代码的地方</span></div><div class="line">	<span class="meta">@Override</span></div><div class="line">	<span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onHandleIntent</span><span class="params">(Intent intent)</span> </span>&#123;</div><div class="line">		Log.i(TAG, <span class="string">"Received an intent: "</span> + intent);</div><div class="line">	&#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在外界使用 Context.startService(Intent) 即可开启服务。</p>
<h1 id="2-使用-AlarmManager-定时启动服务"><a href="#2-使用-AlarmManager-定时启动服务" class="headerlink" title="2. 使用 AlarmManager 定时启动服务"></a>2. 使用 AlarmManager 定时启动服务</h1><p>一个基本的定时启动代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 首先获取服务启动的 intent</span></div><div class="line">Intent i = PollService.newIntent(context);</div><div class="line"><span class="comment">// 将其放入 PendingIntent 中</span></div><div class="line">PendingIntent pi = PendingIntent.getService(context, <span class="number">0</span>, i, <span class="number">0</span>);</div><div class="line"><span class="comment">// 获取 AlarmManager 服务</span></div><div class="line">AlarmManager alarmManager = (AlarmManager)</div><div class="line">        context.getSystemService(Context.ALARM_SERVICE);</div><div class="line"><span class="comment">// 如果开启服务</span></div><div class="line"><span class="keyword">if</span> (isOn) &#123;</div><div class="line">	<span class="comment">// 将这个 PendingIntent 放到 AlarmManager 中定时启动</span></div><div class="line">    alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,</div><div class="line">            SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);</div><div class="line">&#125; <span class="keyword">else</span> &#123;</div><div class="line">	<span class="comment">// 如果没有开启服务，就让 AlarmManager 撤销该定时器</span></div><div class="line">    alarmManager.cancel(pi);</div><div class="line">    <span class="comment">// 它自己也要撤销</span></div><div class="line">    pi.cancel();</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="2-1-PendingIntent"><a href="#2-1-PendingIntent" class="headerlink" title="2.1 PendingIntent"></a>2.1 PendingIntent</h2><p>PendingIntent 是一种 token 对象。调用 PendingIntent.getService(…)方法获取 PendingIntent 时，我们告诉操作系统：“请记住， 我需要使用 startService(Intent)方法发送这个 intent。”随后，调用 PendingIntent 对象的 send()方法时，操作系统会按照要求发送原来封装的 intent。<br>PendingIntent 真正精妙的地方在于，将 PendingIntent token 交给其他应用使用时，它是代表当前应用发送 token 对象的。另外，PendingIntent 本身存在于操作系统而不是 token 里。如果不顾及别人感受的话，也可以在交给别人一个 PendingIntent 对象后，立即撤销它，让 send()方法什么也做不了。<strong>如果使用同一个 intent 请求 PendingIntent 两次，得到的 PendingIntent 仍会是同一个。</strong>我们可借此测试某个 PendingIntent 是否已存在，或撤销已发出的 PendingIntent。</p>
<p><code>PendingIntent.getService(...)</code> 方法打包了启动服务的方法的调用。它有四个参数:一个用来发送 intent 的 Context，一个区分 PendingIntent 来源的请求代码，一个待发送的 Intent 对象以及一组用来决定如何创建 PendingIntent 的标志符。</p>
<h2 id="2-2-使用-AlarmManager"><a href="#2-2-使用-AlarmManager" class="headerlink" title="2.2 使用 AlarmManager"></a>2.2 使用 AlarmManager</h2><p>我们用 AlarmManager.setInexactRepeating(…) 方法开启了定时启动，该方法同样具有四个参数: 一个描述定时器时间基准的常量，定时器启动的时间，定时器循环的时间间隔以及一个到时要发送的 PendingIntent。</p>
<ol>
<li>AlarmManager.ELAPSED_REALTIME 是基准时间值 ， 这表明我们是以 SystemClock. elapsedRealtime()走过的时间来确定何时启动时间的。也就是说，经过一段指定的时间，就启动定时器。假如使用 AlarmManager.RTC，启动基准时间就是当前时刻(例如，System. currentTimeMillis())。也就是说，一旦到了某个固定时刻，就启动定时器。</li>
<li>时间间隔由我们自己确定，不过推荐使用 AlarmManager 自身定义的常量。</li>
</ol>
<h2 id="2-3-获取定时器激活状态"><a href="#2-3-获取定时器激活状态" class="headerlink" title="2.3 获取定时器激活状态"></a>2.3 获取定时器激活状态</h2><p>由于我们在代码中撤销定时器的同时也撤销了 PendingIntent，所以通过发送一个 <code>PendingIntent.FLAG_NO_CREATE</code> 标志给 getService 方法可以获取这个 PendingIntent 存在状态。</p>
<h1 id="3-通知"><a href="#3-通知" class="headerlink" title="3. 通知"></a>3. 通知</h1><p>如果服务需要与用户沟通，通知信息(notification)总是一个不错的选择。通知信息是指显示在通知抽屉上的消息条目，用户可向下滑动屏幕读取。 想要发送通知信息，首先要创建 Notification 对象。</p>
<p>Notification 需使用构造对象来创建。完整的 Notification 至少应包括:</p>
<ul>
<li>在 Lollipop 之前的设备上，首次显示通知信息时，在状态栏上显示的 ticker text(Lollipop<br>之后，ticker text 不再显示在状态栏上，但仍与可访问性服务相关);</li>
<li>在状态栏上显示的图标(在 Lollipop 之前的设备上，图标在 ticker text 消失后出现); </li>
<li>代表通知信息自身，在通知抽屉中显示的视图;</li>
<li>待触发的 PendingIntent，用户点击抽屉中的通知信息时触发。</li>
</ul>
<p>完成 Notification 对象的创建后，可调用 NotificationManager 系统服务的 notify(int, Notification)方法发送它。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">Resources resources = getResources();</div><div class="line">Intent i = PhotoGalleryActivity.newIntent(<span class="keyword">this</span>);</div><div class="line">PendingIntent pi = PendingIntent.getActivity(<span class="keyword">this</span>, <span class="number">0</span>, i, <span class="number">0</span>);</div><div class="line"></div><div class="line">Notification notification = <span class="keyword">new</span> NotificationCompat.Builder(<span class="keyword">this</span>)</div><div class="line">        .setTicker(resources.getString(R.string.new_pictures_title))</div><div class="line">        .setSmallIcon(android.R.drawable.ic_menu_report_image)</div><div class="line">        .setContentTitle(resources.getString(R.string.new_pictures_title))</div><div class="line">        .setContentText(resources.getString(R.string.new_pictures_text))</div><div class="line">        .setContentIntent(pi)</div><div class="line">        .setAutoCancel(<span class="keyword">true</span>)</div><div class="line">        .build();</div><div class="line"></div><div class="line">NotificationManagerCompat notificationManager =</div><div class="line">        NotificationManagerCompat.from(<span class="keyword">this</span>);</div><div class="line">notificationManager.notify(<span class="number">0</span>, notification);</div></pre></td></tr></table></figure>
<ol>
<li>首先，调用 setTicker(CharSequence)和 setSmallIcon (int)方法，配置 ticker text 和小图标。</li>
<li>然后配置 Notification 在下拉抽屉中的外观。图标的值来自于 setSmallIcon(int) 方法 ， 而设置标题和显示文字则需分别调用 setContentTitle (CharSequence)和 setContentText(CharSequence)方法。</li>
<li>接下来，须指定用户点击 Notification 消息时所触发的动作行为。这里使用的是 PendingIntent。用户在下拉抽屉中点击 Notification 消息时，传入 setContentIntent(PendingIntent)方法的 PendingIntent 会被触发。</li>
<li>调用 setAutoCancel (true)方法可调整上述行为。一旦执行了 setAutoCancel(true)设置方法，用户点击 Notification 消息时，该消息就会从消息抽屉中删除。</li>
<li>最后，从当前 context 中取出一个 NotificationManagerCompat 实例，然后调用 Notifi- cationManagerCompat.notify(…)方法贴出消息。传入的整数参数是通知消息的标识符，在整个应用中该值应该是唯一的。如果使用同一 ID 发送两条消息，则第二条消息会替换掉第一条消息。在实际开发中，这也是进度条或其他动态视觉效果的实现方式。</li>
</ol>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-20-cj0ajvovk001t1js6sxp30uob/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-20-cj0ajvovk001t1js6sxp30uob/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十五）—— 第25章 搜索
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-20T16:59:10+08:00">
                2017-02-20
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-20-cj0ajvovk001t1js6sxp30uob/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-20-cj0ajvovk001t1js6sxp30uob/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了 ToolBar 中的 SearchView 的使用以及 SharedPreferences 的简单使用。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/37f3b9fa2dd7d7183de7e1bf600c44a6f59af26a" target="_blank" rel="external">完成第25章</a></p>
</blockquote>
<h1 id="1-SearchView"><a href="#1-SearchView" class="headerlink" title="1. SearchView"></a>1. SearchView</h1><p>SearchView 是一个 ActionView（操作视图），所谓操作视图，就是可以内置在工具栏中的视图。SearchView 可以让整个搜索界面完全内置在应用的工具栏中。</p>
<h2 id="1-1-SearchView-的建立"><a href="#1-1-SearchView-的建立" class="headerlink" title="1.1 SearchView 的建立"></a>1.1 SearchView 的建立</h2><p>首先我们建立菜单资源：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">menu</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line">      <span class="attr">xmlns:app</span>=<span class="string">"http://schemas.android.com/apk/res-auto"</span>&gt;</div><div class="line"></div><div class="line">    <span class="tag">&lt;<span class="name">item</span></span></div><div class="line">        <span class="attr">android:id</span>=<span class="string">"@+id/menu_item_search"</span></div><div class="line">        <span class="attr">android:title</span>=<span class="string">"@string/search"</span></div><div class="line">        <span class="attr">app:actionViewClass</span>=<span class="string">"android.support.v7.widget.SearchView"</span></div><div class="line">        <span class="attr">app:showAsAction</span>=<span class="string">"ifRoom"</span> /&gt;</div><div class="line"><span class="tag">&lt;/<span class="name">menu</span>&gt;</span></div></pre></td></tr></table></figure>
<p>可以发现我们多了一个 <code>app:actionViewClass</code> 属性，这个就是指定这个 item 的 actionView 类。</p>
<p>现在只要我们在 onCreateOptionsMenu 中引用该菜单资源，就能看到 SearchView 了。</p>
<h2 id="1-2-SearchView-的使用"><a href="#1-2-SearchView-的使用" class="headerlink" title="1.2 SearchView 的使用"></a>1.2 SearchView 的使用</h2><p>我们现在能看到 SearchView，当然还要让其能够进行相应的工作：首先获取实例，然后设置回调方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreateOptionsMenu</span><span class="params">(Menu menu, MenuInflater inflater)</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>.onCreateOptionsMenu(menu, inflater);</div><div class="line">    inflater.inflate(R.menu.fragment_photo_gallery, menu);</div><div class="line"></div><div class="line">	<span class="comment">// 在这里获取这个 ActionView</span></div><div class="line">    MenuItem searchItem = menu.findItem(R.id.menu_item_search);</div><div class="line">    <span class="keyword">final</span> SearchView searchView = (SearchView) searchItem.getActionView();</div><div class="line"></div><div class="line">    searchView.setOnQueryTextListener(<span class="keyword">new</span> SearchView.OnQueryTextListener() &#123;</div><div class="line">        <span class="meta">@Override</span></div><div class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onQueryTextSubmit</span><span class="params">(String query)</span> </span>&#123;</div><div class="line">            <span class="comment">// 点击提交后</span></div><div class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        <span class="meta">@Override</span></div><div class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onQueryTextChange</span><span class="params">(String newText)</span> </span>&#123;</div><div class="line">        	<span class="comment">// 文字改变时调用</span></div><div class="line">            Log.d(TAG, <span class="string">"QueryTextChange: "</span> + newText);</div><div class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">        &#125;</div><div class="line">    &#125;);</div><div class="line"></div><div class="line">    searchView.setOnSearchClickListener(<span class="keyword">new</span> View.OnClickListener() &#123;</div><div class="line">        <span class="meta">@Override</span></div><div class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</div><div class="line">            String query = QueryPreferences.getStoredQuery(getActivity());</div><div class="line">            searchView.setQuery(query, <span class="keyword">false</span>);</div><div class="line">        &#125;</div><div class="line">    &#125;);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="2-使用-SharedPreferences-实现轻量级数据存储"><a href="#2-使用-SharedPreferences-实现轻量级数据存储" class="headerlink" title="2. 使用 SharedPreferences 实现轻量级数据存储"></a>2. 使用 SharedPreferences 实现轻量级数据存储</h1><p>shared preferences 本质上就是文件系统中的文件，可使用 SharedPreferences 类读写它。 SharedPreferences 实例用起来更像一个键值对仓库(类似于 Bundle)，但它可以通过持久化存储保存数据。键值对中的键为字符串，而值是原子数据类型。进一步查看 shared preferences 文件可知，它们实际上是一种简单的 XML 文件，但 SharedPreferences 类已屏蔽了读写文件的实现细节。shared preferences 文件保存在应用沙盒中，所以，类似密码这样的敏感信息不应该用它来保存。<br>要获得定制的 SharedPreferences 实例 ， 可使用 Context.getSharedPreferences (String,int)方法。然而，在实际开发中，我们并不关心具体是什么样的 SharedPreferences 实例，只要它能共享于整个应用就可以了。这种情况下，最好使用<code>PreferenceManager. getDefaultSharedPreferences(Context)</code>方法，该方法会返回具有私有权限和默认名称的实例(仅在当前应用内可用)。</p>
<p>一个示例的 SharedPreferences 操作类如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QueryPreferences</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String PREF_SEARCH_QUERY = <span class="string">"searchQuery"</span>;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getStoredQuery</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> PreferenceManager.getDefaultSharedPreferences(context)</div><div class="line">                .getString(PREF_SEARCH_QUERY, <span class="keyword">null</span>);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">setStoredQuery</span><span class="params">(Context context, String query)</span> </span>&#123;</div><div class="line">        PreferenceManager.getDefaultSharedPreferences(context)</div><div class="line">                .edit()</div><div class="line">                .putString(PREF_SEARCH_QUERY, query)</div><div class="line">                .apply();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-15-cj0ajvovb001h1js6jzqsooor/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-15-cj0ajvovb001h1js6jzqsooor/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十四）—— 第24章 Looper、Handler 和 HandlerThread
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-15T16:19:10+08:00">
                2017-02-15
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-15-cj0ajvovb001h1js6jzqsooor/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-15-cj0ajvovb001h1js6jzqsooor/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章顾名思义，讲了 Looper，Handler，以及线程之间的交互。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/c68af985ad2fed691ff399ec8ba4519dd4dcf8f7" target="_blank" rel="external">完成24章但未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/c001631637043bcaf1d288a06c81988e03ecec11" target="_blank" rel="external">完成24章挑战1</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/7323d8b21706f8398896f5fadd1a2612dd6c8b8b" target="_blank" rel="external">完成24章挑战2</a></p>
</blockquote>
<p>在本章，我们要使用一个新的线程，并且使用其中 Looper 来完成下载图片的工作。为什么不直接在 AsyncTask 的 doInBackground 中添加网络下载代码呢？那样就要每次下载一张，直到完成全部100张的下载。最后才会执行 onPostExecute(…)方法，让所有下载的图片全部显示在 RecyclerView 视图中，这样既耗时又耗内存。AsyncTask 是执行后台线程的最简单方式，但它不适用于那些重复且长时间运行的任务。</p>
<h1 id="1-各种定义"><a href="#1-各种定义" class="headerlink" title="1. 各种定义"></a>1. 各种定义</h1><h2 id="1-1-Looper"><a href="#1-1-Looper" class="headerlink" title="1.1 Looper"></a>1.1 Looper</h2><p>Android 系统中，线程有一个消息队列(message queue)。使用消息队列的线程叫作消息循环(message loop)。消息循环会循环检查队列上是否有新消息。消息循环由线程和 looper 组成。Looper 对象管理着线程的消息队列。<br>主线程就是个消息循环，因此也拥有 looper。主线程的所有工作都是由其 looper 完成的。looper 不断从消息队列中抓取消息，然后完成消息指定的任务。</p>
<h2 id="1-2-Message"><a href="#1-2-Message" class="headerlink" title="1.2 Message"></a>1.2 Message</h2><p>Message 类是 Handler 的子类用于 Handler 传递消息，它有好几个实例变量，其中有三个需在实现时定义。 </p>
<ul>
<li>what:用户定义的 int 型消息代码，用来描述消息。</li>
<li>obj:随消息发送的用户指定对象。</li>
<li>target:处理消息的 Handler。<br>Message 的 target 是 Handler 类的一个实例。创建 Message 时，它会自动与一个 Handler 相关联。Message 待处理时，Handler 对象负责触发消息处理事件。</li>
</ul>
<h2 id="1-3-Handler"><a href="#1-3-Handler" class="headerlink" title="1.3 Handler"></a>1.3 Handler</h2><p>Handler 可看作 message handler 的简称。Handler 不仅仅是处理 Message 的目标(target)，也是创建和发布 Message 的接口。创建 Handler 时，它会自动和该线程的 Looper 相关联。</p>
<h2 id="1-4-三者关系"><a href="#1-4-三者关系" class="headerlink" title="1.4 三者关系"></a>1.4 三者关系</h2><ul>
<li>一个 Thread 仅有一个 Looper，也就是一个消息队列</li>
<li>一个 Thread 可以有多个 Handler，共用一个消息队列</li>
<li>一个 Message 只对应一个 Handler，一个 Handler 可以有多个 Message</li>
</ul>
<h2 id="1-5-一个典型-Looper-的处理过程"><a href="#1-5-一个典型-Looper-的处理过程" class="headerlink" title="1.5 一个典型 Looper 的处理过程"></a>1.5 一个典型 Looper 的处理过程</h2><p>首先需要准备好 Looper，然后建立一个其他线程能引用的 Handler，在需要的时候让 Handler 发消息给 Looper 让其执行。</p>
<h1 id="2-HandlerThread"><a href="#2-HandlerThread" class="headerlink" title="2. HandlerThread"></a>2. HandlerThread</h1><p>HandlerThread 类帮我们完成了建立 Looper 的过程，因此，只要继承它就能省去一些工作，并且避免一些风险。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// ThumbnailDowloadler.java 省略了许多内容</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThumbnailDowloader</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">HandlerThread</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MESSAGE_DOWNLOAD = <span class="number">0</span>;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> Boolean mHasQuit = <span class="keyword">false</span>;</div><div class="line">    <span class="keyword">private</span> Handler mRequestHandler;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onLooperPrepared</span><span class="params">()</span> </span>&#123;</div><div class="line">    	<span class="comment">// 新建一个 Handler</span></div><div class="line">        mRequestHandler = <span class="keyword">new</span> Handler() &#123;</div><div class="line">            <span class="meta">@Override</span></div><div class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</div><div class="line">                <span class="keyword">if</span> (msg.what == MESSAGE_DOWNLOAD) &#123;</div><div class="line">                    <span class="comment">// get a photo and refresh view</span></div><div class="line">                &#125;</div><div class="line">            &#125;</div><div class="line">        &#125;;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">queueThumbnail</span><span class="params">(T target, String url)</span> </span>&#123;</div><div class="line">        <span class="comment">// 外部调用，发消息给 Handler</span></div><div class="line">        mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)</div><div class="line">                    .sendToTarget();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clearQueue</span><span class="params">()</span> </span>&#123;</div><div class="line">        mRequestHandler.removeMessages(MESSAGE_DOWNLOAD);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>主线程中是这样的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">mThumbnailDownloader = <span class="keyword">new</span> ThumbnailDownloader&lt;&gt;();</div><div class="line">mThumbnailDownloader.start();</div><div class="line">mThumbnailDownloader.getLooper();</div><div class="line"><span class="comment">// 在需要的时候调用</span></div><div class="line">mThumbnailDownloader.queueThumbnail(holder, url);</div></pre></td></tr></table></figure>
<h1 id="3-线程交互"><a href="#3-线程交互" class="headerlink" title="3. 线程交互"></a>3. 线程交互</h1><p>主线程现在能够适时调用这个线程的方法，用于下载图片了。但是还存在一个问题，那就是下载线程下载完一个任务以后如何更新视图呢？我们知道 UI 只能在主线程里更新，所以我们采用在主线程里声明一个 Handler，传递给下载线程，让下载线程在下载完成后在主线程执行更新操作。因为不能直接引用主线程的方法，故而在这里用到了回调。</p>
<h2 id="3-1-下载线程中"><a href="#3-1-下载线程中" class="headerlink" title="3.1 下载线程中"></a>3.1 下载线程中</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// ThumbnailDownloader，也就是下载线程中</span></div><div class="line"></div><div class="line"><span class="comment">// 成员声明</span></div><div class="line"><span class="keyword">private</span> Handler mResponseHandler;</div><div class="line"><span class="keyword">private</span> ThumbnailDowloadListener&lt;T&gt; mThumbnailDownloadListener;</div><div class="line"></div><div class="line"><span class="comment">// 回调接口</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ThumbnailDowloadListener</span>&lt;<span class="title">T</span>&gt; </span>&#123;</div><div class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onThumbnailDownloaded</span><span class="params">(T target, Bitmap thumbnail)</span></span>;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setThumbnailDownloaderListener</span><span class="params">(ThumbnailDowloadListener&lt;T&gt; listener)</span> </span>&#123;</div><div class="line">    mThumbnailDownloadListener = listener;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="comment">// 通过构造函数传递主线程的 Handler</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThumbnailDowloader</span><span class="params">(Handler responseHandler)</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>(TAG);</div><div class="line">    mResponseHandler = responseHandler;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>这样，主线程通过调用这些方法，就能够让下载线程获取到主线程的 Handler 和回调接口实例。</p>
<h2 id="3-2-主线程中"><a href="#3-2-主线程中" class="headerlink" title="3.2 主线程中"></a>3.2 主线程中</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 成员声明</span></div><div class="line"><span class="keyword">private</span> ThumbnailDowloader&lt;PhotoHolder&gt; mThumbnailDownloader;</div><div class="line"></div><div class="line"><span class="comment">// 传递实例给下载线程</span></div><div class="line"><span class="comment">// 这个 Handler 在主线程中建立，所以是和主线程 Looper 相关联的</span></div><div class="line">Handler responseHandler = <span class="keyword">new</span> Handler(); </div><div class="line">mThumbnailDownloader = <span class="keyword">new</span> ThumbnailDowloader&lt;&gt;(responseHandler);</div><div class="line">mThumbnailDownloader.setThumbnailDownloaderListener(</div><div class="line">	<span class="keyword">new</span> ThumbnailDowloader.ThumbnailDowloadListener&lt;PhotoHolder&gt;() &#123;</div><div class="line">	   	<span class="meta">@Override</span></div><div class="line">	   <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onThumbnailDownloaded</span><span class="params">(PhotoHolder target, Bitmap thumbnail)</span> </span>&#123;</div><div class="line">	   		Drawable drawable = <span class="keyword">new</span> BitmapDrawable(getResources(), thumbnail);</div><div class="line">			target.bindDrawable(drawable);</div><div class="line">    	&#125;</div><div class="line">	&#125;</div><div class="line">);</div></pre></td></tr></table></figure>
<h2 id="3-3-在下载线程中给主线程-Looper-发送消息"><a href="#3-3-在下载线程中给主线程-Looper-发送消息" class="headerlink" title="3.3 在下载线程中给主线程 Looper 发送消息"></a>3.3 在下载线程中给主线程 Looper 发送消息</h2><p>现在，通过 mResponseHandler，下载线程能够访问与主线程 Looper 绑定的 Handler。同时，还有 ThumbnailDownloadListener 使用返回的 Bitmap 执行 UI 更新操作。具体来说， 就是通过 onThumbnailDownloaded 实现，使用新下载的 Bitmap 来设置 PhotoHolder 的 Drawable。<br>和在下载线程上把下载图片的请求放入消息队列类似，我们也可以返回定制 Message 给主线程，要求显示已下载图片。不过，这需要另一个 Handler 子类，以及一个 handleMessage(…) 覆盖方法。方便起见，我们转而使用另一个方便的 Handler 方法——post(Runnable)。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">mResponseHandler.post(<span class="keyword">new</span> Runnable() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (mRequestMap.get(target) != url ||</div><div class="line">                mHasQuit) &#123;</div><div class="line">            <span class="keyword">return</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        mRequestMap.remove(target);</div><div class="line">        mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
<p>在这里，新建的 Runnable 对象会被当成 Message 的回调方法，直接执行 run() 方法，所以相当于发送一个消息，里面写明了怎么做，而不是把对象和消息类型发给 Handler，让 Handler 决定怎么做。</p>
<h1 id="4-挑战练习"><a href="#4-挑战练习" class="headerlink" title="4. 挑战练习"></a>4. 挑战练习</h1><h2 id="4-1-缓存层的添加"><a href="#4-1-缓存层的添加" class="headerlink" title="4.1 缓存层的添加"></a>4.1 缓存层的添加</h2><p>首先我们能够意识到，这个缓存应该放在下载线程中，因为所有下载的处理都在其中。LruCache 是一种类似键值对的存在，在图片缓存中，我们理所应当地可以将图片的 url 作为键，对应的 Bitmap 作为值，在下载一张图片之前先检查 Cache 中是否存在这个图片，不存在再下载。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 其中省略了之前写过的大量过程</span></div><div class="line"></div><div class="line"><span class="keyword">private</span> LruCache&lt;String, Bitmap&gt; mCache;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onLooperPrepared</span><span class="params">()</span> </span>&#123;</div><div class="line">	……</div><div class="line">	<span class="comment">// 建立 Cache</span></div><div class="line">    <span class="keyword">int</span> maxCacheSize = <span class="number">4</span> * <span class="number">1024</span> * <span class="number">1024</span>; <span class="comment">// 4MiB</span></div><div class="line">    mCache = <span class="keyword">new</span> LruCache&lt;&gt;(maxCacheSize);</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">handleRequest</span><span class="params">(<span class="keyword">final</span> T target)</span> </span>&#123;</div><div class="line">    <span class="keyword">try</span> &#123;</div><div class="line">        <span class="keyword">final</span> String url = mRequestMap.get(target);</div><div class="line"></div><div class="line">        <span class="keyword">if</span> (url == <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="keyword">return</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        <span class="keyword">final</span> Bitmap bitmap;</div><div class="line">        <span class="keyword">if</span> (mCache.get(url) == <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="keyword">byte</span>[] bitmapBytes = <span class="keyword">new</span> FlickrFetchr().getUrlBytes(url);</div><div class="line">            bitmap = BitmapFactory</div><div class="line">                    .decodeByteArray(bitmapBytes, <span class="number">0</span>, bitmapBytes.length);</div><div class="line">            Log.i(TAG, <span class="string">"Bitmap created"</span>);</div><div class="line"></div><div class="line">            mCache.put(url, bitmap);</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            bitmap = mCache.get(url);</div><div class="line">            Log.i(TAG, <span class="string">"Bitmap from cache"</span>);</div><div class="line">        &#125;</div><div class="line">        ……</div><div class="line">    &#125; <span class="keyword">catch</span> (IOException ioe) &#123;</div><div class="line">        Log.e(TAG, <span class="string">"Error downloading image"</span>, ioe);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="4-2-预加载"><a href="#4-2-预加载" class="headerlink" title="4.2 预加载"></a>4.2 预加载</h2><p>我用了较为笨的方法实现简单的预加载，那就是在 onBindViewHolder 的时候挨个加载对应 item 的前十个和后十个并放到缓存中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 下载线程中的方法</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">handlePreload</span><span class="params">(String url)</span> </span>&#123;</div><div class="line">    <span class="keyword">try</span> &#123;</div><div class="line">        <span class="keyword">if</span> (url == <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="keyword">return</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        <span class="keyword">if</span> (mCache.get(url) == <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="keyword">byte</span>[] bitmapBytes = <span class="keyword">new</span> FlickrFetchr().getUrlBytes(url);</div><div class="line">            Bitmap bitmap = BitmapFactory</div><div class="line">                    .decodeByteArray(bitmapBytes, <span class="number">0</span>, bitmapBytes.length);</div><div class="line">            mCache.put(url, bitmap);</div><div class="line">        &#125;</div><div class="line"></div><div class="line">    &#125; <span class="keyword">catch</span> (IOException ioe) &#123;</div><div class="line">        Log.e(TAG, <span class="string">"Error preloading image"</span>, ioe);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 主线程中的调用</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onBindViewHolder</span><span class="params">(PhotoHolder holder, <span class="keyword">int</span> position)</span> </span>&#123;</div><div class="line">	……</div><div class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = Math.max(<span class="number">0</span>, position - <span class="number">10</span>);</div><div class="line">            i &lt; Math.min(mGalleryItems.size() - <span class="number">1</span>, position + <span class="number">10</span>);</div><div class="line">            i ++ ) &#123;</div><div class="line">        Log.i(TAG, <span class="string">"Preload position"</span> + i);</div><div class="line">        mThumbnailDownloader.queuePreloadThumbnail(mGalleryItems.get(i).getUrl());</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-11-cj0ajvovc001k1js6bilf9jju/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-11-cj0ajvovc001k1js6bilf9jju/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十三）—— 第23章 HTTP 与后台任务
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-11T17:21:10+08:00">
                2017-02-11
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-11-cj0ajvovc001k1js6bilf9jju/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-11-cj0ajvovc001k1js6bilf9jju/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了如何使用 android 系统的网络连接，并介绍了格式化 JSON 和多线程编程 AsyncTask 的使用。另外，挑战练习里还结合了 Gson 库的使用。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/62310fed3abea3bb08151656539dffbf035a69f6" target="_blank" rel="external">完成23章但未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/4e47fb20d3285fe7cc61ccce140f467c5935a6d3" target="_blank" rel="external">完成23章挑战1：使用 Gson</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/5445c65491e29e2a6fd349667c8621b4bed903e6" target="_blank" rel="external">完成23章挑战2：添加分页</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/074e528d9160a82d9881e184f7132a1a8fa67b34" target="_blank" rel="external">完成23章挑战3：动态调整网格列</a></p>
</blockquote>
<h1 id="1-网络连接基本"><a href="#1-网络连接基本" class="headerlink" title="1. 网络连接基本"></a>1. 网络连接基本</h1><p>首先要在 Manifest 文件中请求网络权限</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.INTERNET"</span> /&gt;</span></div></pre></td></tr></table></figure>
<p>然后我们建立一个网络请求的函数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// FlickrFetchr.java</span></div><div class="line"><span class="comment">// 参数是 url 字符串，并且需要抛出 IO 错误</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] getUrlBytes(String urlSpec) <span class="keyword">throws</span> IOException &#123;</div><div class="line">    URL url = <span class="keyword">new</span> URL(urlSpec);</div><div class="line">    <span class="comment">// 打开连接</span></div><div class="line">    HttpURLConnection connection = (HttpURLConnection) url.openConnection();</div><div class="line"></div><div class="line">    <span class="keyword">try</span> &#123;</div><div class="line">    	<span class="comment">// 建立两个流对象</span></div><div class="line">        ByteArrayOutputStream out = <span class="keyword">new</span> ByteArrayOutputStream();</div><div class="line">        <span class="comment">// 使用 getInputStream() 方法时才会真正发送 GET 请求</span></div><div class="line">        <span class="comment">// 如果要使用 POST 请求，需要调用 getOutputStream()</span></div><div class="line">        InputStream in = connection.getInputStream();</div><div class="line">		<span class="comment">// 如果连接失败就抛出错误</span></div><div class="line">        <span class="keyword">if</span> (connection.getResponseCode() != HttpURLConnection.HTTP_OK) &#123;</div><div class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IOException(connection.getResponseMessage() +</div><div class="line">                    <span class="string">": with"</span> +</div><div class="line">                    urlSpec);</div><div class="line">        &#125;</div><div class="line">        </div><div class="line">        <span class="comment">// 建立一个计数器</span></div><div class="line">        <span class="keyword">int</span> bytesRead = <span class="number">0</span>;</div><div class="line">        <span class="comment">// 建立一个缓存 buffer</span></div><div class="line">        <span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</div><div class="line">        <span class="comment">// 用 InputStream.read 将数据读取到 buffer 中，</span></div><div class="line">        <span class="comment">// 然后写到 OutputStream 中</span></div><div class="line">        <span class="keyword">while</span> ((bytesRead = in.read(buffer)) &gt; <span class="number">0</span>) &#123;</div><div class="line">            out.write(buffer, <span class="number">0</span>, bytesRead);</div><div class="line">        &#125;</div><div class="line">        <span class="comment">// 之后一定要关闭 OutputStream</span></div><div class="line">        out.close();</div><div class="line">        <span class="keyword">return</span> out.toByteArray();</div><div class="line">    &#125; <span class="keyword">finally</span> &#123;</div><div class="line">    	<span class="comment">// 最后要关闭连接</span></div><div class="line">        connection.disconnect();</div><div class="line">    &#125;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getUrlString</span><span class="params">(String urlSpec)</span> <span class="keyword">throws</span> IOException </span>&#123;</div><div class="line">	<span class="comment">// 将结果转换成 String</span></div><div class="line">    <span class="keyword">return</span> <span class="keyword">new</span> String(getUrlBytes(urlSpec));</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="2-线程与主线程"><a href="#2-线程与主线程" class="headerlink" title="2. 线程与主线程"></a>2. 线程与主线程</h1><p>网络连接需要时间，Web 服务器可能需要1~2秒的时间来响应访问请求，文件下载则耗时更久。考虑到这个因素，Android 禁止任何主线程网络连接行为。即使强行在主线程中进行网络连接，Android 也会抛出 NetworkOnMainThreadException 异常。</p>
<p>这是为什么呢?要想知道，首先要了解什么是线程，什么是主线程以及主线程的用途是什么。<br><strong>线程是个单一执行序列。单个线程中的代码会逐步执行。所有 Android 应用的运行都是从主线程开始的。然而，主线程不是线程那样的预定执行序列。相反，它处于一个无限循环的运行状态，等待着用户或系统触发事件的发生。事件触发后，主线程便负责执行代码，以响应这些事件。</strong></p>
<p>主线程运行着所有更新 UI 的代码，其中包括响应 activity 的启动、按钮的点击等不同 UI 相关事件的代码。(由于响应的事件基本都与用户界面相关，主线程有时也叫作 UI 线程。)<br>事件处理循环让 UI 代码得以按顺序执行。这可以保证任何事件处理都不会发生冲突，同时代码也能够快速响应执行。</p>
<p>而网络连接相比其他任务更耗时。等待响应期间，用户界面毫无反应，这可能会导致应用无响应(Application Not Responding,ANR)现象发生，也就是一个弹框，要求你关闭应用。<br>怎样使用后台线程最容易呢？答案就是使用 AsyncTask 类</p>
<h1 id="3-AsyncTask"><a href="#3-AsyncTask" class="headerlink" title="3. AsyncTask"></a>3. AsyncTask</h1><h2 id="3-1-AsyncTask-的生命"><a href="#3-1-AsyncTask-的生命" class="headerlink" title="3.1 AsyncTask 的生命"></a>3.1 AsyncTask 的生命</h2><p>AsyncTask 类可以重写的方法和一个进程的生命过程对应：</p>
<ul>
<li><code>onPreExecute()</code> 执行之前</li>
<li><code>onProgressUpdate()</code> 更新进展</li>
<li><code>doInBackground()</code> 在线程中真正要完成的事</li>
<li><code>onPostExecute()</code> 完成之后要做的事（在 UI 线程中执行）</li>
<li><code>onCancelled()</code>    退出之后</li>
</ul>
<h2 id="3-2-AsyncTask-的三个参数"><a href="#3-2-AsyncTask-的三个参数" class="headerlink" title="3.2 AsyncTask 的三个参数"></a>3.2 AsyncTask 的三个参数</h2><p>其中模板的三个类类型参数（不能是基础类型）分别是：输入、进度、结果。</p>
<h3 id="3-2-1-第一个参数：输入"><a href="#3-2-1-第一个参数：输入" class="headerlink" title="3.2.1 第一个参数：输入"></a>3.2.1 第一个参数：输入</h3><p>第一个类型参数可指定输入参数的类型。可参考以下示例使用该参数:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">AsyncTask&lt;String,Void,Void&gt; task = <span class="keyword">new</span> AsyncTask&lt;String,Void,Void&gt;() &#123;</div><div class="line">	<span class="function"><span class="keyword">public</span> Void <span class="title">doInBackground</span><span class="params">(String... params)</span> </span>&#123; </div><div class="line">		<span class="keyword">for</span> (String parameter : params) &#123;</div><div class="line">			Log.i(TAG, <span class="string">"Received parameter: "</span> + parameter);</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">	&#125;</div><div class="line">&#125;;</div></pre></td></tr></table></figure>
<p>输入参数传入 execute(…)方法(可接受一个或多个参数): task.execute(“第一个参数”, “第二个参数”, “……”);<br>然后，再把这些变量参数传递给 doInBackground(…)方法。 </p>
<h3 id="3-2-2-第二个参数：进度"><a href="#3-2-2-第二个参数：进度" class="headerlink" title="3.2.2 第二个参数：进度"></a>3.2.2 第二个参数：进度</h3><p>第二个类型参数可指定发送进度更新需要的类型。以下为示例代码:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">final</span> ProgressBar gestationProgressBar = <span class="comment">/* 一个特定的进度条 */</span>;</div><div class="line">gestationProgressBar.setMax(<span class="number">42</span>); <span class="comment">/* 最大的进度 */</span></div><div class="line">AsyncTask&lt;Void,Integer,Void&gt; haveABaby = <span class="keyword">new</span> AsyncTask&lt;Void,Integer,Void&gt;() &#123;</div><div class="line">	<span class="function"><span class="keyword">public</span> Void <span class="title">doInBackground</span><span class="params">(Void... params)</span> </span>&#123;</div><div class="line">		<span class="keyword">while</span> (!babyIsBorn()) &#123;</div><div class="line">			Integer weeksPassed = getNumberOfWeeksPassed();</div><div class="line">          publishProgress(weeksPassed); <span class="comment">// 关键，将参数发送到 onProgressUpdate</span></div><div class="line">          patientlyWaitForBaby();</div><div class="line">		&#125; </div><div class="line">	&#125;</div><div class="line">	</div><div class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProgressUpdate</span><span class="params">(Integer... params)</span> </span>&#123;</div><div class="line">		<span class="keyword">int</span> progress = params[<span class="number">0</span>];</div><div class="line">		gestationProgressBar.setProgress(progress);</div><div class="line">	&#125; </div><div class="line">&#125;;</div><div class="line"><span class="comment">/* call when you want to execute the AsyncTask */</span></div><div class="line">haveABaby.execute();</div></pre></td></tr></table></figure>
<p>进度更新通常发生在执行的后台进程中。问题是，在后台进程中无法完成必要的 UI 更新。因此 AsyncTask 提供了 publishProgress(…)和 onProgressUpdate(…)方法。<br> 其工作方式是这样的 : 在后台线程中 , 从 doInBackground(…) 方法中调用 publishProgress(…)方法。这样 onProgressUpdate(…)方法便能够在 UI 线程上调用。因此，在 onProgressUpdate(…)方法中执行 UI 更新就可行了，但必须在 doInBackground(…) 方法中使用 publishProgress(…)方法对它们进行管控。</p>
<h3 id="3-2-3-第三个参数：结果"><a href="#3-2-3-第三个参数：结果" class="headerlink" title="3.2.3 第三个参数：结果"></a>3.2.3 第三个参数：结果</h3><p>第三个类型参数是处理结果返回的类型参数。下面是本章的示例代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// PhotoGalleryFragment.java</span></div><div class="line"></div><div class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">FetchItemsTask</span> <span class="keyword">extends</span> <span class="title">AsyncTask</span>&lt;<span class="title">Integer</span>, <span class="title">Void</span>, <span class="title">List</span>&lt;<span class="title">GalleryItem</span>&gt;&gt; </span>&#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">protected</span> List&lt;GalleryItem&gt; <span class="title">doInBackground</span><span class="params">(Integer... params)</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> <span class="keyword">new</span> FlickrFetchr().fetchItems(params[<span class="number">0</span>]);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onPostExecute</span><span class="params">(List&lt;GalleryItem&gt; galleryItems)</span> </span>&#123;</div><div class="line">        mItems = galleryItems;</div><div class="line">        setAdapter();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>第三个参数就是在 doInBackground 中返回的结果，我们需要从后台请求 API 返回的 JSON 数据，然后将其格式化，返回的就是我们需要的数据。</p>
<h1 id="4-JSON-数据解析"><a href="#4-JSON-数据解析" class="headerlink" title="4. JSON 数据解析"></a>4. JSON 数据解析</h1><p>什么是 JSON 数据呢？JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于 JavaScript 的一个子集。JSON 采用完全独立于语言的文本格式，但是也使用了类似于C语言家族的习惯（包括C, C++, C#, Java, JavaScript, Perl, Python 等）。这些特性使 JSON 成为理想的数据交换语言。</p>
<p>JSON 对象是一系列包含在{ }中的名值对。JSON 数组是包含在[ ]中用逗号隔开的 JSON 对象列表。对象彼此嵌套形成层级关系。详细的语法可以查看<a href="http://json.org/" target="_blank" rel="external">JSON 官网</a>。</p>
<p>JSON 这种数据格式在同样基于这些结构的编程语言之间交换十分方便，所以网络服务器端越来越多地开始用 JSON 来交换数据，我们在这章使用的 API 同样如此。</p>
<h2 id="一个例子"><a href="#一个例子" class="headerlink" title="一个例子"></a>一个例子</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 为节省版面，去掉了无关的属性</span></div><div class="line">&#123;</div><div class="line">  <span class="string">"photos"</span>: &#123;</div><div class="line">    <span class="string">"page"</span>: <span class="number">1</span>,</div><div class="line">    <span class="string">"pages"</span>: <span class="number">10</span>,</div><div class="line">    <span class="string">"photo"</span>: [</div><div class="line">      &#123;</div><div class="line">        <span class="string">"id"</span>: <span class="string">"31987348504"</span>,</div><div class="line">        <span class="string">"title"</span>: <span class="string">"Penny"</span>,</div><div class="line">        <span class="string">"url_s"</span>: <span class="string">"https://farm3.staticflickr.com/2915/31987348504_9a949c482d_m.jpg"</span>,</div><div class="line">      &#125;,</div><div class="line">      &#123;</div><div class="line">        <span class="string">"id"</span>: <span class="string">"31987352214"</span>,</div><div class="line">        <span class="string">"title"</span>: <span class="string">""</span>,</div><div class="line">        <span class="string">"url_s"</span>: <span class="string">"https://farm1.staticflickr.com/455/31987352214_58428f3a9d_m.jpg"</span>,</div><div class="line">      &#125;</div><div class="line">    ]</div><div class="line">  &#125;,</div><div class="line">  <span class="string">"stat"</span>: <span class="string">"ok"</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>对应的解析代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 解析时用 try…catch，要抛出 JSONException 防止程序崩溃</span></div><div class="line"><span class="comment">// JSONObject 构造方法解析传入的 JSON 数据后</span></div><div class="line"><span class="comment">// 会生成与原始 JSON 数据对应的对象树</span></div><div class="line">JSONObject jsonBody = <span class="keyword">new</span> JSONObject(jsonString);</div><div class="line"></div><div class="line"><span class="comment">// 顶层 JSONObject 对应着原始数据最外层的&#123; &#125;。它包含了一个叫作 photos 的嵌套 JSONObject</span></div><div class="line">JSONObject photosJsonObject = jsonBody.getJSONObject(<span class="string">"photos"</span>);</div><div class="line"></div><div class="line"><span class="comment">// 这个嵌套对象又包含了一个叫作 photo 的 JSONArray</span></div><div class="line">JSONArray photoJsonArray = photosJsonObject.getJSONArray(<span class="string">"photo"</span>);</div><div class="line"></div><div class="line"><span class="comment">// 这个嵌套数组中又包含了一组 JSONObject</span></div><div class="line"><span class="comment">// 这些 JSONObeject 就是要获取的一张张图片的元数据</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; photoJsonArray.length(); i++) &#123;</div><div class="line">    JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);</div><div class="line">    GalleryItem item = <span class="keyword">new</span> GalleryItem();</div><div class="line">    item.setId(photoJsonObject.getString(<span class="string">"id"</span>));</div><div class="line">    item.setCaption(photoJsonObject.getString(<span class="string">"title"</span>));</div><div class="line">    <span class="keyword">if</span> (!photoJsonObject.has(<span class="string">"url_s"</span>)) &#123;</div><div class="line">        <span class="keyword">continue</span>;</div><div class="line">    &#125;</div><div class="line">    item.setUrl(photoJsonObject.getString(<span class="string">"url_s"</span>));</div><div class="line">    items.add(item);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>解析完成后就可以在 AsyncTask 的 onPostExecute 中对 UI 进行更新了。</p>
<h1 id="5-挑战练习"><a href="#5-挑战练习" class="headerlink" title="5. 挑战练习"></a>5. 挑战练习</h1><p>本章的挑战练习难度依次递增，考验了我们很多知识。</p>
<h2 id="5-1-使用-Gson-库解析-JSON-数据"><a href="#5-1-使用-Gson-库解析-JSON-数据" class="headerlink" title="5.1 使用 Gson 库解析 JSON 数据"></a>5.1 使用 Gson 库解析 JSON 数据</h2><p>Gson 是 Google 官方推荐的 JSON 解析库，使用 Gson 不用写任何解析代码，它能自动将 JSON 数据映射为 Java 对象。</p>
<h3 id="5-1-1-添加-Gson-依赖"><a href="#5-1-1-添加-Gson-依赖" class="headerlink" title="5.1.1 添加 Gson 依赖"></a>5.1.1 添加 Gson 依赖</h3><p>在 File -&gt; Project Structure -&gt; Dependencies 中添加 gson 依赖</p>
<h3 id="5-1-2-构建对应的-POJO-类"><a href="#5-1-2-构建对应的-POJO-类" class="headerlink" title="5.1.2 构建对应的 POJO 类"></a>5.1.2 构建对应的 POJO 类</h3><p>由于不想更改原本的 GalleryItem 类，并且想让成员变量的命名符合 java 的命名规范，我使用了 <code>@SerializedName()</code> 注解，这个注解注明了 Gson 在转换时对应的键名。并且构建了一个新的类，用于匹配对应的 API 结构：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// PhotoBean.java</span></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PhotoBean</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String STATUS_OK = <span class="string">"ok"</span></div><div class="line">            , STATUS_FAILED = <span class="string">"fail"</span>;</div><div class="line"></div><div class="line">    <span class="meta">@SerializedName</span>(<span class="string">"photos"</span>)</div><div class="line">    <span class="keyword">private</span> PhotosInfo mPhotoInfo;</div><div class="line">    <span class="meta">@SerializedName</span>(<span class="string">"stat"</span>)</div><div class="line">    <span class="keyword">private</span> String mStatus;</div><div class="line">    <span class="meta">@SerializedName</span>(<span class="string">"message"</span>)</div><div class="line">    <span class="keyword">private</span> String mMessage;</div><div class="line"></div><div class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PhotosInfo</span> </span>&#123;</div><div class="line">        <span class="meta">@SerializedName</span>(<span class="string">"photo"</span>)</div><div class="line">        List&lt;GalleryItem&gt; mPhoto;</div><div class="line"></div><div class="line">        <span class="function"><span class="keyword">public</span> List&lt;GalleryItem&gt; <span class="title">getPhoto</span><span class="params">()</span> </span>&#123;</div><div class="line">            <span class="keyword">return</span> mPhoto;</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">    <span class="comment">// 省略 getter 和 setter</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="5-1-3-使用-Gson"><a href="#5-1-3-使用-Gson" class="headerlink" title="5.1.3 使用 Gson"></a>5.1.3 使用 Gson</h3><p>Gson 的使用再简单不过了，与上面的代码相比有云泥之别：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">PhotoBean photoBean = (PhotoBean) <span class="keyword">new</span> Gson()</div><div class="line">        .fromJson(jsonString, PhotoBean.class);</div></pre></td></tr></table></figure>
<p>不过记得要抛出 JsonSyntaxException。</p>
<h2 id="5-2-分页显示"><a href="#5-2-分页显示" class="headerlink" title="5.2 分页显示"></a>5.2 分页显示</h2><p>这个挑战的需求是：如果我们下滑最底部，就在后面添加下一页的内容。<br>所以在 url 的生成中我们还要加入 page 这个参数。我加入了一个成员变量 mNextPage 用于记录下次要请求的页面， 然后添加了一个常量 MAX_PAGES 用于控制最大请求页数。</p>
<h3 id="5-2-1-RecyclerView-onScrollListener"><a href="#5-2-1-RecyclerView-onScrollListener" class="headerlink" title="5.2.1 RecyclerView.onScrollListener"></a>5.2.1 <code>RecyclerView.onScrollListener</code></h3><p>onScrollListener 有两个可以重写的方法，一个是 onScrollStateChanged()，还有一个是 onScrolled，对我们这个需求来说，显然 onScrollStateChanged 比较合适，ScrollState 也有三种：</p>
<ul>
<li><code>SCROLL_STATE_IDLE</code>: 视图没有被拖动，处于静止  </li>
<li><code>SCROLL_STATE_DRAGGING</code>： 视图正在拖动中 </li>
<li><code>SCROLL_STATE_SETTLING</code>： 视图在惯性滚动 </li>
</ul>
<p>这个挑战最关键的就是如何判断滑到最底端。首先滑动到最底端时前两个状态其实都可以，但是滑动到最底这个信息只有 LayoutManager 知道，我们可以直接看代码分析：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> RecyclerView.OnScrollListener onButtomListener = </div><div class="line">		<span class="keyword">new</span> RecyclerView.OnScrollListener() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onScrollStateChanged</span><span class="params">(RecyclerView recyclerView, <span class="keyword">int</span> newState)</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>.onScrollStateChanged(recyclerView, newState);</div><div class="line">        <span class="comment">// 首先获取 LayoutManager</span></div><div class="line">        GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();</div><div class="line">        <span class="comment">// 然后可以找到最后显示的位置，一旦滚动就会获取该位置</span></div><div class="line">        mLastPosition = layoutManager.findLastCompletelyVisibleItemPosition();</div><div class="line">        <span class="comment">// 如果静止的时候最后的位置大于等于数据个数</span></div><div class="line">        <span class="comment">// 而且前一个任务完成时（防止多次重复）</span></div><div class="line">        <span class="keyword">if</span> (newState == RecyclerView.SCROLL_STATE_IDLE</div><div class="line">                &amp;&amp; mLastPosition &gt;= mPhotoAdapter.getItemCount() - <span class="number">1</span>) &#123;</div><div class="line">            <span class="keyword">if</span> (mFetchItemsTask.getStatus() == AsyncTask.Status.FINISHED) &#123;</div><div class="line">            	<span class="comment">// 下一页加一，在小于最大页数时</span></div><div class="line">            	<span class="comment">// 弹出 Toast 表示正在加载</span></div><div class="line">            	<span class="comment">// 然后打开一个新任务，加载下一页</span></div><div class="line">                mNextPage++;</div><div class="line">                <span class="keyword">if</span> (mNextPage &lt;= MAX_PAGES) &#123;</div><div class="line">                    Toast.makeText(getActivity(), <span class="string">"waiting to load ……"</span>, Toast.LENGTH_SHORT).show();</div><div class="line">                    <span class="comment">// AsyncTask 只能执行一次，所以需要新建</span></div><div class="line">                    mFetchItemsTask = <span class="keyword">new</span> FetchItemsTask();</div><div class="line">                    mFetchItemsTask.execute(mNextPage);</div><div class="line">                &#125; <span class="keyword">else</span> &#123;</div><div class="line">                	<span class="comment">// 滑到最底提示已经到头了</span></div><div class="line">                    Toast.makeText(getActivity(), <span class="string">"This is the end!"</span>, Toast.LENGTH_SHORT).show();</div><div class="line">                &#125;</div><div class="line">            &#125;</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;;</div></pre></td></tr></table></figure>
<h3 id="5-2-2-添加数据并展示"><a href="#5-2-2-添加数据并展示" class="headerlink" title="5.2.2 添加数据并展示"></a>5.2.2 添加数据并展示</h3><p>我在 Adapter 中加入了一个 addData 方法，将新的数据加入到数据集中，然后使用 notifyDataSetChanged 方法更新视图。</p>
<p>然后修改了 setAdapter 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setAdapter</span><span class="params">()</span> </span>&#123;</div><div class="line">    <span class="keyword">if</span> (isAdded()) &#123;</div><div class="line">        <span class="keyword">if</span> (mPhotoAdapter == <span class="keyword">null</span>) &#123;</div><div class="line">            mPhotoAdapter = <span class="keyword">new</span> PhotoAdapter(mItems);</div><div class="line">            mPhotoRecyclerView.setAdapter(mPhotoAdapter);</div><div class="line">            mPhotoRecyclerView.addOnScrollListener(onButtomListener);</div><div class="line">        &#125; <span class="keyword">else</span> &#123;</div><div class="line">            mPhotoAdapter.addData(mItems);</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="5-3-动态调整网格列"><a href="#5-3-动态调整网格列" class="headerlink" title="5.3 动态调整网格列"></a>5.3 动态调整网格列</h2><p>使用 OnGlobalLayoutListener 即可：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">mPhotoRecyclerView.getViewTreeObserver()</div><div class="line">.addOnGlobalLayoutListener(<span class="keyword">new</span> ViewTreeObserver.OnGlobalLayoutListener() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onGlobalLayout</span><span class="params">()</span> </span>&#123;</div><div class="line">		<span class="comment">// 计算列数，以 1080p 屏幕显示3列为基准</span></div><div class="line">        <span class="keyword">int</span> columns = mPhotoRecyclerView.getWidth() / <span class="number">350</span>;</div><div class="line">        <span class="comment">// 重新设置 LayoutManager、Adapter 和 Listener</span></div><div class="line">        mPhotoRecyclerView.setLayoutManager(<span class="keyword">new</span> GridLayoutManager(getActivity(), columns));</div><div class="line">        mPhotoRecyclerView.setAdapter(mPhotoAdapter);</div><div class="line">        mPhotoRecyclerView.addOnScrollListener(onButtomListener);</div><div class="line">        <span class="comment">// 滚动到之前看到的位置</span></div><div class="line">        mPhotoRecyclerView.getLayoutManager().scrollToPosition(mLastPosition);</div><div class="line">        <span class="comment">//将 GlobalLayoutListener 去掉以避免多次触发</span></div><div class="line">        mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(<span class="keyword">this</span>);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-07-cj0ajvovh001q1js67gyo016y/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-07-cj0ajvovh001q1js67gyo016y/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十二）—— 第22章 深入学习 intent 和任务
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-07T16:13:10+08:00">
                2017-02-07
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-07-cj0ajvovh001q1js67gyo016y/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-07-cj0ajvovh001q1js67gyo016y/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章构建了一个最简单的 Launcher 应用，可以让我们深入理解 intent，intent 过滤器以及 Android 应用间的交互，还介绍了进程和任务的联系与区别。<br>GitHub 地址：</p>
<p><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/fd445e40074aa92c9ff87d2881970b13f4556811" target="_blank" rel="external">完成第22章,未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/372285397ee0d30fa4e3f5d2787448d0fa29dd95" target="_blank" rel="external">完成挑战</a></p>
</blockquote>
<hr>
<p>我们要构建一个 Launcher 应用，它的大概形式是一个列表，通过点击可以进入相应的应用，这个时候我们需要获取所有的可启动主 activity。</p>
<h1 id="1-解析隐式-intent"><a href="#1-解析隐式-intent" class="headerlink" title="1. 解析隐式 intent"></a>1. 解析隐式 intent</h1><p>可启动的主 activity 都有包含 MAIN 操作和 LAUNCHER 类别的 intent 过滤器，一般在 AndroidManifest.xml 中的形式如下：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">activity</span> <span class="attr">android:name</span>=<span class="string">".XXXXActivity"</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></div><div class="line">		<span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">"android.intent.action.MAIN"</span>/&gt;</span></div><div class="line">		<span class="tag">&lt;<span class="name">category</span> <span class="attr">android:name</span>=<span class="string">"android.intent.category.LAUNCHER"</span>/&gt;</span></div><div class="line">	<span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">activity</span>&gt;</span></div></pre></td></tr></table></figure>
<p>所以我们要建立一个 intent，然后用 PackageManager 来解析这个 intent，获取所有匹配的 activity。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">Intent startupIntent = <span class="keyword">new</span> Intent(Intent.ACTION_MAIN);</div><div class="line">startupIntent.addCategory(Intent.CATEGORY_LAUNCHER);</div><div class="line"></div><div class="line">PackageManager pm = getActivity().getPackageManager();</div><div class="line">List&lt;ResolveInfo&gt; activities = pm.queryIntentActivities(startupIntent, <span class="number">0</span>);</div></pre></td></tr></table></figure>
<blockquote>
<p>startActivity(Intent intent) 方法会在 Intent 对象中添加 Intent.CATEGORY_DEFAULT ，而一般的应用主 activity 可能不包含 CATEGORY_DEFAULT 类别，所以并不能用 startActivity 启动。</p>
</blockquote>
<p>接下来要知道这些应用的名字，我们查询到的 ResolveInfo 对象中包含的 activity 标签都是可启动的主 activity， 那么其标签名一般也就是应用名。为了保证列表的美观，我们对这些 activity 的名字按首字母排序：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">Collections.sort(activities, <span class="keyword">new</span> Comparator&lt;ResolveInfo&gt;() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compare</span><span class="params">(ResolveInfo o1, ResolveInfo o2)</span> </span>&#123;</div><div class="line">        PackageManager pm = getActivity().getPackageManager();</div><div class="line">        <span class="keyword">return</span> String.CASE_INSENSITIVE_ORDER.compare(</div><div class="line">                o1.loadLabel(pm).toString(),</div><div class="line">                o2.loadLabel(pm).toString());</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
<h1 id="2-运行时创建显式-intent"><a href="#2-运行时创建显式-intent" class="headerlink" title="2. 运行时创建显式 intent"></a>2. 运行时创建显式 intent</h1><p>在点击列表中某项的时候，我们要响应并打开，所以在 onClickListener 中添加显式的 intent，打开对应的 activity：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</div><div class="line">    ActivityInfo activityInfo = mResolveInfo.activityInfo;</div><div class="line"></div><div class="line">    Intent i = <span class="keyword">new</span> Intent(Intent.ACTION_MAIN)</div><div class="line">            .setClassName(activityInfo.applicationInfo.packageName,</div><div class="line">                    activityInfo.name);</div><div class="line"></div><div class="line">    startActivity(i);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>我们使用了方法 setClassName ，使用包名和类名创建一个 Intent 对象</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Intent <span class="title">setClassName</span><span class="params">(String packageName, String className)</span></span></div></pre></td></tr></table></figure>
<p>该方法和一个 Intent 的构造方法 <code>public Intent(Context packageContext, Class&lt;?&gt; cls)</code>结果相同，都是为 Intent 添加了 ComponentName，当然，Intent 本身也有一个 setComponent 的方法。使用 setClassName 能够自动创建 ComponentName，所需要的实现代码相对较少。</p>
<h1 id="3-任务与后退栈"><a href="#3-任务与后退栈" class="headerlink" title="3. 任务与后退栈"></a>3. 任务与后退栈</h1><p>首先给出定义，<strong>任务</strong>：acivity 栈。</p>
<p>栈底部的 activity 通常称为基 activity。用户可以看到栈顶的 activity。用户点击后退键时，栈顶 activity 会弹出栈外。如果当前屏幕上显示的是基 activity，点击后退键，系统会退回主屏幕。默认情况下，新 activity 都在当前任务中启动。在 CriminalIntent 应用中，无论何时启动新 activity，它都会被添加到当前任务中。即使要启动的 activity 不属于本应用，它同样也在当前任务中启动。</p>
<h2 id="3-1-任务间切换"><a href="#3-1-任务间切换" class="headerlink" title="3.1 任务间切换"></a>3.1 任务间切换</h2><p>一般来说，我们使用 overview screen 在任务间切换，或者关闭某项任务。</p>
<blockquote>
<p>overview 的其他叫法还有：任务管理器、最近使用屏、最近应用、最近任务等，启动方法就是点击 Recents 按钮，一般是个方块或者连个重叠的长方形。</p>
</blockquote>
<h2 id="3-2-启动新任务"><a href="#3-2-启动新任务" class="headerlink" title="3.2 启动新任务"></a>3.2 启动新任务</h2><p>因为我们做一个 Launcher 应用，所以要为打开的 activity 新建一个任务。所以需要在建立 intent 的时候添加一个标志：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</div><div class="line">    ActivityInfo activityInfo = mResolveInfo.activityInfo;</div><div class="line"></div><div class="line">    Intent i = <span class="keyword">new</span> Intent(Intent.ACTION_MAIN)</div><div class="line">            .setClassName(activityInfo.applicationInfo.packageName,</div><div class="line">                    activityInfo.name)</div><div class="line">            <span class="comment">// 添加一个 New Task 的标识就能在启动时新建一个任务</span></div><div class="line">            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);</div><div class="line"></div><div class="line">    startActivity(i);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="3-3-将应用作为设备的主界面"><a href="#3-3-将应用作为设备的主界面" class="headerlink" title="3.3 将应用作为设备的主界面"></a>3.3 将应用作为设备的主界面</h2><p>只要在 Manifest 文件中的 <code>&lt;intent-filter&gt;</code>标签中添加值为 HOME 和 DEFAULT 的 category 即可</p>
<h1 id="4-进程"><a href="#4-进程" class="headerlink" title="4 进程"></a>4 进程</h1><h2 id="4-1-进程是什么"><a href="#4-1-进程是什么" class="headerlink" title="4.1 进程是什么"></a>4.1 进程是什么</h2><p>对象需要内存和虚拟机的支持才能存在。进程是操作系统创建的、供应用对象生存以及应用运行的地方。进程通常占用由操作系统管理着的系统资源，如内存、网络端口以及打开的文件等。进程还拥有至少一个(可能多个)执行线程。在 Android 系统中，进程总会有一个运行的虚拟机。</p>
<p>尽管存在未知的异常情况，但总的来说，Android 世界里的每个应用组件都仅与一个进程相关联。应用伴随着自己的进程一起完成创建，该进程同时也是应用中所有组件的默认进程。<br>(虽然组件可以指派给不同的进程，但我们推荐使用默认进程。如果确实需要在不同进程中运行应用组件，通常也可以借助多线程来达到目的。相比多进程的使用，Android 多线程的使用更加简单。) </p>
<p>每一个 activity 实例都仅存在于一个进程和一个任务中。这也是进程与任务的唯一相似之处。任<br> 务只包含 activity，这些 activity 通常可以来自于不同的应用;而进程则包含了应用的全部运行代码和对象。 </p>
<h2 id="4-2-进程和任务的区别"><a href="#4-2-进程和任务的区别" class="headerlink" title="4.2 进程和任务的区别"></a>4.2 进程和任务的区别</h2><p>进程与任务很容易让人混淆，主要原因在于它们不仅在概念上有某种重叠，而且通常都是以其所属应用的名称被人提及的。我们以短信应用和联系人应用为例，看看以下具体场景就会明白了（首先清理掉 overview screen 中的所有任务）。</p>
<ul>
<li><strong>打开短信应用：</strong>这里我们新建了一个任务，也新建了一个短信的进程</li>
<li><strong>点击选择收件人，这会打开联系人应用让我们选择目标联系人：</strong> 我们仍然只有一个短信任务，其中包含了两个应用的 activity，也就是说新建了联系人的进程，这样便有了两个进程</li>
<li><strong>直接切回主界面（而不是后退回去），打开联系人应用：</strong>这样，我们多了一个联系人的任务，并且在联系人进程中新增了一个联系人 activity 的实例。</li>
</ul>
<p>此外，Android 并没有提供方法用来终止任务，不过，我们可以终止进程。应用商店中那些宣称自己是任务终止器的应用，实际上都是进程终止器。</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-02-04-cj0ajvove001n1js6o61f48o3/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-02-04-cj0ajvove001n1js6o61f48o3/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十一）—— 第21章 XML drawable
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-02-04T16:48:10+08:00">
                2017-02-04
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-02-04-cj0ajvove001n1js6o61f48o3/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-02-04-cj0ajvove001n1js6o61f48o3/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章介绍了常用的 XML drawable，包括 shape，state list 和 layer list。还介绍了 9-patch 图片和 mipmap 目录等知识。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/b037c855ab0f8be47795982ea1b735b4573819d7" target="_blank" rel="external">完成第21章</a></p>
</blockquote>
<h1 id="1-XML-drawable"><a href="#1-XML-drawable" class="headerlink" title="1. XML drawable"></a>1. XML drawable</h1><p>在 Android 世界里，凡是要在屏幕上绘制的东西都可以叫作 drawable，比如抽象图形、 Drawable 类的子类、位图图像等。我们之前用来封装图片的 BitmapDrawable 就是一种 drawable。 本章我们使用的 drawable 都定义在 XML 文件中，可以归为一类，统称为 XML drawable。</p>
<h2 id="1-1-为什么要使用-XML-drawable"><a href="#1-1-为什么要使用-XML-drawable" class="headerlink" title="1.1 为什么要使用 XML drawable"></a>1.1 为什么要使用 XML drawable</h2><p>XML drawable 用起来方便灵活，不仅用法多样，还易于更新维护。搭配使用 shape drawable<br>和 layer list drawable 可以做出复杂的背景图，连图像编辑器都省了。更改应用的配色更是简单，直接修改 XML drawable 中的颜色就行了。<br>另外，XML drawable 独立于屏幕像素密度，它们直接定义在 drawable 目录中，不需要加屏幕密度资源修饰符。如果是普通图像，就需要准备多个版本，以适配不同屏幕像素密度的设备;而 XML drawable 只要定义一次，就能在任何设备的屏幕上表现出色。</p>
<h2 id="1-2-shape-drawable"><a href="#1-2-shape-drawable" class="headerlink" title="1.2 shape drawable"></a>1.2 shape drawable</h2><p>一个常见的圆形 shape drawable 如下：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">shape</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line">       <span class="attr">android:shape</span>=<span class="string">"oval"</span>&gt;</div><div class="line"></div><div class="line">	<span class="tag">&lt;<span class="name">solid</span></span></div><div class="line">		<span class="attr">android:color</span>=<span class="string">"@color/red"</span>/&gt;</div><div class="line"></div><div class="line">	<span class="tag">&lt;<span class="name">stroke</span></span></div><div class="line">   		<span class="attr">android:width</span>=<span class="string">"4dp"</span></div><div class="line">   		<span class="attr">android:color</span>=<span class="string">"@color/dark_red"</span>/&gt;</div><div class="line"><span class="tag">&lt;/<span class="name">shape</span>&gt;</span></div></pre></td></tr></table></figure>
<p>该 shape drawable 定义了一个带有暗红色圆环的填充红色的椭圆。</p>
<ul>
<li>shape 标签中 android:shape 属性定义了其形状为椭圆（当然，如果需要是正圆，只需要把控件的高宽设为相等即可），还可以是长方形，线条，梯形等图形;</li>
<li>solid 标签定义填充，其中 android:color 定义其填充颜色（默认透明）</li>
<li>stroke 标签定义边框，属性都类似</li>
<li>还有更多标签和属性，可以在这里找到：<a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#Shape" target="_blank" rel="external">shape drawable</a></li>
</ul>
<h2 id="1-3-state-list-drawable"><a href="#1-3-state-list-drawable" class="headerlink" title="1.3 state list drawable"></a>1.3 state list drawable</h2><p>为了在控件的不同状态下切换不同的 drawable， 我们用到了 state list drawable，它会根据按钮的状态改变使用的 drawable，一个例子如下：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">selector</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">item</span> <span class="attr">android:drawable</span>=<span class="string">"@drawable/button_beat_box_pressed"</span></span></div><div class="line">          <span class="attr">android:state_pressed</span>=<span class="string">"true"</span>/&gt;</div><div class="line">    <span class="tag">&lt;<span class="name">item</span> <span class="attr">android:drawable</span>=<span class="string">"@drawable/button_beat_box_normal"</span>/&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">selector</span>&gt;</span></div></pre></td></tr></table></figure>
<p>将一个 drawable 资源文件定义为 selector，便是让系统根据状态选择。状态有按下，禁用，聚焦以及激活等状态，可以根据不同的状态使用不同的 drawable。</p>
<p>还有更多标签和属性，可以在这里找到：<a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList" target="_blank" rel="external">state list drawable</a></p>
<h2 id="1-4-layer-list-drawable"><a href="#1-4-layer-list-drawable" class="headerlink" title="1.4 layer list drawable"></a>1.4 layer list drawable</h2><p>layer list 可以将多个 drawable 组成一个 drawable 并可以指定其摆放层次和位置，可以组成一些复杂的效果。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">layer-list</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span></div><div class="line">        <span class="tag">&lt;<span class="name">shape</span> <span class="attr">android:shape</span>=<span class="string">"oval"</span>&gt;</span></div><div class="line"></div><div class="line">            <span class="tag">&lt;<span class="name">solid</span></span></div><div class="line">                <span class="attr">android:color</span>=<span class="string">"@color/red"</span>/&gt;</div><div class="line"></div><div class="line">        <span class="tag">&lt;/<span class="name">shape</span>&gt;</span></div><div class="line">    <span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span></div><div class="line">        <span class="tag">&lt;<span class="name">shape</span> <span class="attr">android:shape</span>=<span class="string">"rectangle"</span>&gt;</span></div><div class="line">            <span class="tag">&lt;<span class="name">stroke</span></span></div><div class="line">                <span class="attr">android:width</span>=<span class="string">"4dp"</span></div><div class="line">                <span class="attr">android:color</span>=<span class="string">"@color/dark_red"</span>/&gt;</div><div class="line">        <span class="tag">&lt;/<span class="name">shape</span>&gt;</span></div><div class="line">    <span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">layer-list</span>&gt;</span></div></pre></td></tr></table></figure>
<p>这个 layer list 将一个矩形叠放在一个椭圆上。还有更多的属性可以查看 <a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#LayerList" target="_blank" rel="external">layer list drawable</a></p>
<h1 id="2-9-patch-图像（九宫格图像）"><a href="#2-9-patch-图像（九宫格图像）" class="headerlink" title="2. 9-patch 图像（九宫格图像）"></a>2. 9-patch 图像（九宫格图像）</h1><p>有时候，按钮背景图必须用到普通图片。那么，如果按钮需要以不同尺寸显示，背景图该如何变化呢?如果按钮的宽度大于背景图的宽度，图片会被拉伸。拉伸的图片会有很好的效果吗? 朝一个方向拉伸背景图很可能会让图片失去原样，所以得想个办法控制图片拉伸方式。</p>
<p>9-patch 就是这样一种方式，它将图像分成3×3的网格，即由9 patch 组成的网格。网格角落部分不会被缩放，边缘部分的4个 patch 只按一个维度缩放，而中间部分则按两个维度缩放，如下图：</p>
<img src="/2017-02-04-cj0ajvove001n1js6o61f48o3/9-patch.png" alt="9-patch 示意图" title="9-patch 示意图">
<p>9-patch 图像和普通 PNG 图像十分相似，只有两处不同:9-patch 图像文件名以.9.png 结尾，图像边缘具有1像素宽度的边框。这个边框用以指定9-patch 图像的中间位置。边框像素绘制为黑线， 以表明中间位置，边缘部分则用透明色表示。</p>
<p>在 Android Studio 中，右键单击想要转换为 9-patch 的图像文件选择创建 9-patch 文件，然后双击打开，画上黑边即可。旁边会显示不同方向拉伸后的效果。</p>
<img src="/2017-02-04-cj0ajvove001n1js6o61f48o3/9-patch效果图.png" alt="9-patch 效果图" title="9-patch 效果图">
<h1 id="3-mipmap-目录"><a href="#3-mipmap-目录" class="headerlink" title="3. mipmap 目录"></a>3. mipmap 目录</h1><p>应用要用到图像，就一般会针对不同的设备尺寸准备不同尺寸的图片，再分别放入 drawable-mdpi 和 drawable-hdpi 这样的文件夹。然后，按名字引用它们。 剩下的就交给 Android 了，它会根据当前设备的屏幕密度调用相应的图片。<br>这样就会存在一个问题：发布出去的 APK 文件包含了项目 drawable 目录里的所有图片，哪怕是从来不会用到的图片，这会让应用体积无意义地增大。为减轻负担，有人想到针对设备定制 APK，比如 mdpi APK 一个，hdpi APK 一个，等等。有关 APK 分包的详细信息，可参阅<a href="https://developer.android.com/studio/build/configure-apk-splits.html" target="_blank" rel="external">Build Multiple APKs</a></p>
<p>相反，设置分包以后，有的时候会需要不同分辨率的图标，比如启动器不同，需要的图标清晰度不同，所以，要用到 mipmap 目录。APK 分包时，mipmap 资源会全部包含在 APK 文件中。</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-31-cj0ajvov5001b1js65o9yffa6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-31-cj0ajvov5001b1js65o9yffa6/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（二十）—— 第20章 样式与主题
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-31T15:00:10+08:00">
                2017-01-31
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-31-cj0ajvov5001b1js65o9yffa6/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-31-cj0ajvov5001b1js65o9yffa6/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了如何使用样式与主题（style and theme），使应用界面统一化标准化。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/7fd01a912f8e1de5314b08988925a044b839064a" target="_blank" rel="external">完成第20章</a></p>
</blockquote>
<h1 id="1-样式（Style）"><a href="#1-样式（Style）" class="headerlink" title="1. 样式（Style）"></a>1. 样式（Style）</h1><h2 id="1-1-样式的定义"><a href="#1-1-样式的定义" class="headerlink" title="1.1 样式的定义"></a>1.1 样式的定义</h2><h3 id="样式是一组能应用于视图组件的属性，用于复用相同的-UI-特性"><a href="#样式是一组能应用于视图组件的属性，用于复用相同的-UI-特性" class="headerlink" title="样式是一组能应用于视图组件的属性，用于复用相同的 UI 特性"></a>样式是一组能应用于视图组件的属性，用于复用相同的 UI 特性</h3><p>例如我们在 res/values/style.xml 中定义一个样式：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"BeatBoxButton"</span>&gt;</span><span class="xml"></span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:background"</span>&gt;</span>@color/dark_blue<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line">	<span class="comment">&lt;!--其中的 color 资源在 colors.xml 中定义--&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<p>那么使用这个样式的组件就会自动套用这个属性：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">&lt;Button</div><div class="line">	……</div><div class="line">	style="@style/BeatBoxButton"</div><div class="line">	&lt;!--这样就能自动套用这个背景的颜色--&gt;</div><div class="line">	……/&gt;</div></pre></td></tr></table></figure>
<h2 id="1-2-样式的继承"><a href="#1-2-样式的继承" class="headerlink" title="1.2 样式的继承"></a>1.2 样式的继承</h2><h3 id="样式的继承可以在原有样式的基础上添加和覆盖属性"><a href="#样式的继承可以在原有样式的基础上添加和覆盖属性" class="headerlink" title="样式的继承可以在原有样式的基础上添加和覆盖属性"></a>样式的继承可以在原有样式的基础上添加和覆盖属性</h3><ul>
<li><p><strong>继承方式一</strong>：通过命名表示样式继承关系</p>
  <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="comment">&lt;!--用点连接表示继承关系，只能在包内使用--&gt;</span></div><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"BeatBoxButton.Strong"</span>&gt;</span><span class="xml"></span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:textStyle"</span>&gt;</span>bold<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
</li>
<li><p><strong>继承方式二</strong>：用指定父样式的方法继承</p>
  <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="comment">&lt;!--用 parent 属性表示继承关系，可以跨库使用--&gt;</span></div><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"StrongBeatBoxButton"</span> </span></div><div class="line">		<span class="attr">parent</span>=<span class="string">"@style/BeatBoxButton"</span>&gt;<span class="xml"></span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:textStyle"</span>&gt;</span>bold<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
</li>
</ul>
<h1 id="2-主题（Theme）"><a href="#2-主题（Theme）" class="headerlink" title="2. 主题（Theme）"></a>2. 主题（Theme）</h1><p>样式能让一些属性复用，看上去节省了许多工作，在 styles.xml 公共文件中，可以为所有组件定义一套样式属性共用。然而，如果组件多了，需要逐个为所有组件添加它们要用到的样式，工作量是很大的。</p>
<p>这个时候主题就可以派上用场了。可以把主题看作样式的进化加强版，同样是定义一套公共主题属性，样式属性需要逐个添加，而主题属性则会自动应用于整个应用。主题属性能引用颜色这样的外部资源，也能引用其他样式。使用主题，可以简单地说:“所有按钮都使用这个样式。”再也不用找到每个按钮，告诉它们要用哪个主题了。</p>
<h2 id="2-1-主题属性的更改"><a href="#2-1-主题属性的更改" class="headerlink" title="2.1 主题属性的更改"></a>2.1 主题属性的更改</h2><p>主题作用于某个 activity 或者整个应用，这和主题在 manifest 文件中的声明位置有关，如果在 <code>&lt;application&gt;</code> 标签中声明，则是作用于整个应用，在<code>&lt;activity&gt;</code>标签中声明，则是作用于单个 activity。</p>
<p>在 manifest 文件中我们看到整个应用的主题是<code>android:theme=&quot;@style/AppTheme&quot;</code>，按住 Command（Windows 下是 Ctrl），点击 AppTheme 就可以进入其声明的位置，可以看到以下代码：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"AppTheme"</span> <span class="attr">parent</span>=<span class="string">"Theme.AppCompat.Light.DarkActionBar"</span>&gt;</span><span class="undefined"></span></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<p><strong>AppCompat 库自带三大主题</strong>:</p>
<ul>
<li>Theme.AppCompat——深色主题</li>
<li>Theme.AppCompat.Light——浅色主题</li>
<li>Theme.AppCompat.Light.DarkActionBar——带深色工具栏的浅色主题<br>我们把 AppTheme 的父主题修改为 Theme.AppCompat，以便于修改属性。</li>
</ul>
<h3 id="2-1-1-修改主题颜色"><a href="#2-1-1-修改主题颜色" class="headerlink" title="2.1.1 修改主题颜色"></a>2.1.1 修改主题颜色</h3><p>在空空的主题 style 标签中加入几个属性，如下：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"AppTheme"</span> <span class="attr">parent</span>=<span class="string">"Theme.AppCompat"</span>&gt;</span><span class="xml"></span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"colorPrimary"</span>&gt;</span>@color/red<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"colorPrimaryDark"</span>&gt;</span>@color/dark_red<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"colorAccent"</span>&gt;</span>@color/gray<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<ul>
<li>colorPrimary 属性主要用来设置工具栏背景色。由于应用名称是显示在工具栏上的，colorPrimary 也可以称为应用品牌色</li>
<li>colorPrimaryDark 用于屏幕顶部的状态栏。从名字可以看出，它是深色版 colorPrimary。 注意，只有 Lollipop 以后的系统支持状态栏主题色。对于之前的系统，无论指定什么主题色，状态栏都是不变的黑底色。</li>
<li>最后，将 colorAccent 设置为灰色的。这个主题色应该和 colorPrimary 形成反差效果，主要用于给 EditText 这样的组件着色。</li>
</ul>
<h2 id="2-2-主题的抽丝剥茧"><a href="#2-2-主题的抽丝剥茧" class="headerlink" title="2.2 主题的抽丝剥茧"></a>2.2 主题的抽丝剥茧</h2><p>完成了主题配色，我们继续来点深入的，比如可以看看可以覆盖的主题属性都有哪些。在研究诸如有哪些主题属性可用，哪些能覆盖，甚至是有某些属性究竟有什么作用等这样的问题时，几乎没有官方参考文档可以参考，所以要逐级往上查看。</p>
<p>例如，我想找到设置页面背景颜色的属性(随着时间的推移，主题继承关系和层次可能有变，但下面介绍的方法不会变。 想要知道该覆盖哪个属性，就沿着继承树找吧!)：</p>
<h3 id="第一层"><a href="#第一层" class="headerlink" title="第一层"></a>第一层</h3><p>也就是 AppTheme 主题，它原本是个 Android Studio 自动生成的空的主题。</p>
<blockquote>
<p>为什么要生成一个空主题，而不在 manifest 中直接使用<code>Theme.AppCompat.Light.DarkActionBar</code>呢？这就是设计模式的事情了，对于应用本身来说，使用的主题不管父主题是谁，引用的都是 AppTheme 这个主题，相当于定义主题的时候留出了一个 AppTheme 的接口。对于协作开发来说，你不用管我内部如何实现也不用操心该怎么引用，只要使用 AppTheme 就够了。</p>
</blockquote>
<p>我们继续往上找。</p>
<h3 id="第二层"><a href="#第二层" class="headerlink" title="第二层"></a>第二层</h3><p>进入 <code>Theme.AppCompat</code>，可以看到其也是一个空主题：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Theme.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"Base.Theme.AppCompat"</span>/&gt;</span><span class="undefined"></span></div></pre></td></tr></table></figure>
<p>要进入上一层时，可以看到有好几个候选的父主题，这里我们选择 values-v21 目录下的父主题</p>
<h3 id="第三层"><a href="#第三层" class="headerlink" title="第三层"></a>第三层</h3><p>这一层仍然是空主题</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Base.Theme.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"Base.V21.Theme.AppCompat"</span>/&gt;</span><span class="undefined"></span></div></pre></td></tr></table></figure>
<p>那就再往上走</p>
<h3 id="第四层"><a href="#第四层" class="headerlink" title="第四层"></a>第四层</h3><p>终于出现了很多属性</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Base.V21.Theme.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"Base.V7.Theme.AppCompat"</span>&gt;</span><span class="undefined"></span></div><div class="line">	……</div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<p>但是没有我们想要的设置背景的属性，所以要继续往上</p>
<h3 id="第五层"><a href="#第五层" class="headerlink" title="第五层"></a>第五层</h3><p>也定义了很多属性，但是也没有需要的</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Base.V7.Theme.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"Platform.AppCompat"</span>&gt;</span><span class="undefined"></span></div><div class="line">	……</div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<p>继续往上，这里 <code>Platform.AppCompat</code>也有多个版本，我们选择 values-v11 下的父主题</p>
<h3 id="第六层"><a href="#第六层" class="headerlink" title="第六层"></a>第六层</h3><p>空主题，继续往上</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Platform.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"Platform.V11.AppCompat"</span>/&gt;</span><span class="undefined"></span></div></pre></td></tr></table></figure>
<h3 id="第七层"><a href="#第七层" class="headerlink" title="第七层"></a>第七层</h3><p>终于看到了我们想要的属性：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">&lt;!--可以看到这个主题的 parent 是带有 android 命名空间的，表示其来源于 Android 系统--&gt;</span></div><div class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"Platform.V11.AppCompat"</span> <span class="attr">parent</span>=<span class="string">"android:Theme.Holo"</span>&gt;</span><span class="xml"></span></div><div class="line">        ……</div><div class="line">        <span class="comment">&lt;!-- Window colors --&gt;</span></div><div class="line">        ……</div><div class="line">        <span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:colorBackground"</span>&gt;</span>@color/background_material_dark<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line">        ……</div><div class="line"></div><div class="line"><span class="tag">&lt;/<span class="name">style</span>&gt;</span></div></pre></td></tr></table></figure>
<p>所以，最后我们可以在 AppTheme 中覆盖这个属性，设置背景颜色：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"colorBackground"</span>&gt;</span>@color/soothing_blue<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div></pre></td></tr></table></figure>
<h2 id="2-3-修改按钮颜色"><a href="#2-3-修改按钮颜色" class="headerlink" title="2.3 修改按钮颜色"></a>2.3 修改按钮颜色</h2><p>同样的，我们再往上找，能在 <code>android:Theme.Holo</code> 中找到 buttonStyle 属性：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"buttonStyle"</span>&gt;</span>@style/Widget.Holo.Button<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div></pre></td></tr></table></figure>
<p>这样，我们的 BeatBoxButton 可以继承 <code>Widget.Holo.Button</code> 这个样式，然后在 AppTheme 中使用 buttonStyle 属性。</p>
<h1 id="3-引用主题属性"><a href="#3-引用主题属性" class="headerlink" title="3. 引用主题属性"></a>3. 引用主题属性</h1><p>在主题中定义好属性后，可以在 XML 或代码中直接使用它们。<br>在 XML 中引用具体值时(如颜色值)，我们使用@符号。比如 @color/gray 指向某个特定资源。<br>在主题中引用资源时，我们使用?符号。<br>比如：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">Button</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line">	<span class="attr">xmlns:tools</span>=<span class="string">"http://schemas.android.com/tools"</span></div><div class="line">	<span class="attr">android:id</span>=<span class="string">"@+id/list_item_sound_button"</span></div><div class="line">	<span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></div><div class="line">	<span class="attr">android:layout_height</span>=<span class="string">"120dp"</span></div><div class="line">	<span class="attr">android:background</span>=<span class="string">"?attr/colorAccent"</span></div><div class="line">	<span class="attr">tools:text</span>=<span class="string">"Sound name"</span>/&gt;</div></pre></td></tr></table></figure>
<h1 id="4-挑战练习"><a href="#4-挑战练习" class="headerlink" title="4. 挑战练习"></a>4. 挑战练习</h1><p>新建 values-v21 下的 styles.xml 文件，继承自 Widget.Material.Button 即可</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
  </section>

  
  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/page/4/">4</a><a class="extend next" rel="next" href="/page/2/"><i class="fa fa-angle-right"></i></a>
  </nav>


          
          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    <div class="sidebar-inner">

      

      

      <section class="site-overview sidebar-panel sidebar-panel-active">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/images/avatar.jpg"
               alt="kniost" />
          <p class="site-author-name" itemprop="name">kniost</p>
           
              <p class="site-description motion-element" itemprop="description">kniost 的成长与疑惑</p>
          
        </div>
        <nav class="site-state motion-element">

          
            <div class="site-state-item site-state-posts">
              <a href="/archives">
                <span class="site-state-item-count">31</span>
                <span class="site-state-item-name">日志</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-categories">
              <a href="/categories/index.html">
                <span class="site-state-item-count">3</span>
                <span class="site-state-item-name">分类</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-tags">
              <a href="/tags/index.html">
                <span class="site-state-item-count">3</span>
                <span class="site-state-item-name">标签</span>
              </a>
            </div>
          

        </nav>

        
          <div class="feed-link motion-element">
            <a href="/atom.xml" rel="alternate">
              <i class="fa fa-rss"></i>
              RSS
            </a>
          </div>
        

        <div class="links-of-author motion-element">
          
        </div>

        
        

        
        

        


      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  &copy; 
  <span itemprop="copyrightYear">2017</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">kniost</span>
</div>


<div class="powered-by">
  由 <a class="theme-link" href="https://hexo.io">Hexo</a> 强力驱动
</div>

<div class="theme-info">
  主题 -
  <a class="theme-link" href="https://github.com/iissnan/hexo-theme-next">
    NexT.Pisces
  </a>
</div>


        

        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    
    
  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  




  
  <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.0"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.0"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.0"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.0"></script>



  

  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.0"></script>



  



  

    <script type="text/javascript">
      var disqus_shortname = 'kniost-comment-github';
      var disqus_identifier = 'index.html';

      var disqus_title = "";


      function run_disqus_script(disqus_script) {
        var dsq = document.createElement('script');
        dsq.type = 'text/javascript';
        dsq.async = true;
        dsq.src = '//' + disqus_shortname + '.disqus.com/' + disqus_script;
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
      }

      run_disqus_script('count.js');

      

    </script>
  










  
  

  

  

  

  


  

</body>
</html>
